<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/blog/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/blog/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/blog/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/blog/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/blog/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/blog/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/blog/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">





  <link rel="alternate" href="/blog/atom.xml" title="编程那点事" type="application/atom+xml">






<meta property="og:type" content="website">
<meta property="og:title" content="编程那点事">
<meta property="og:url" content="https://lhweb.gitee.io/blog/page/2/index.html">
<meta property="og:site_name" content="编程那点事">
<meta property="og:locale" content="default">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="编程那点事">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/blog/',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: 'Author'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://lhweb.gitee.io/blog/page/2/">





  <title>编程那点事</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="default">

  
  
    
  

  <div class="container sidebar-position-left 
  page-home">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/blog/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">编程那点事</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/blog/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            Home
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/blog/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            Archives
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/" itemprop="url">【PWA学习与实践】(2) PWA的实现</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-28T08:23:08+08:00">
                2018-09-28
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/pwa/" itemprop="url" rel="index">
                    <span itemprop="name">pwa</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h1><p>大家都知道Native app体验确实很好，下载到手机上之后入口也方便。它也有一些缺点:</p>
<ul>
<li>开发成本高(ios和安卓)</li>
<li>软件上线需要审核</li>
<li>版本更新需要将新版本上传到不同的应用商店</li>
<li>想使用一个app就必须去下载才能使用，即使是偶尔需要使用一下下</li>
</ul>
<p>而web网页开发成本低，网站更新时上传最新的资源到服务器即可，用手机带的浏览器打开就可以使用。但是出了体验上比Native app还是差一些，还有一些明显的缺点</p>
<ul>
<li>手机桌面入口不够便捷，想要进入一个页面必须要记住它的url或者加入书签</li>
<li>没网络就没响应，不具备离线能力</li>
<li>不像APP一样能进行消息推送</li>
</ul>
<p>那么什么是PWA呢？</p>
<h1 id="二、What’s-PWA"><a href="#二、What’s-PWA" class="headerlink" title="二、What’s PWA?"></a>二、What’s PWA?</h1><p>PWA全称Progressive Web App，即渐进式WEB应用。</p>
<p>一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能<br>解决了哪些问题？</p>
<ul>
<li>可以添加至主屏幕，点击主屏幕图标可以实现启动动画以及隐藏地址栏</li>
<li>实现离线缓存功能，即使用户手机没有网络，依然可以使用一些离线功能</li>
<li>实现了消息推送</li>
</ul>
<p>它解决了上述提到的问题，这些特性将使得 Web 应用渐进式接近原生 App。</p>
<h1 id="三、PWA的实现"><a href="#三、PWA的实现" class="headerlink" title="三、PWA的实现"></a>三、PWA的实现</h1><h2 id="Manifest实现添加至主屏幕"><a href="#Manifest实现添加至主屏幕" class="headerlink" title="Manifest实现添加至主屏幕"></a>Manifest实现添加至主屏幕</h2><p>index.html</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>Minimal PWA<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, user-scalable=no"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"manifest"</span> <span class="attr">href</span>=<span class="string">"manifest.json"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">href</span>=<span class="string">"main.css"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"icon"</span> <span class="attr">href</span>=<span class="string">"/e.png"</span> <span class="attr">type</span>=<span class="string">"image/png"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>manifest.json</p>
<figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"name"</span>: <span class="string">"Minimal PWA"</span>, <span class="comment">// 必填 显示的插件名称</span></span><br><span class="line">  <span class="string">"short_name"</span>: <span class="string">"PWA Demo"</span>, <span class="comment">// 可选  在APP launcher和新的tab页显示，如果没有设置，则使用name</span></span><br><span class="line">  <span class="string">"description"</span>: <span class="string">"The app that helps you understand PWA"</span>, <span class="comment">//用于描述应用</span></span><br><span class="line">  <span class="string">"display"</span>: <span class="string">"standalone"</span>, <span class="comment">// 定义开发人员对Web应用程序的首选显示模式。standalone模式会有单独的</span></span><br><span class="line">  <span class="string">"start_url"</span>: <span class="string">"/"</span>, <span class="comment">// 应用启动时的url</span></span><br><span class="line">  <span class="string">"theme_color"</span>: <span class="string">"#313131"</span>, <span class="comment">// 桌面图标的背景色</span></span><br><span class="line">  <span class="string">"background_color"</span>: <span class="string">"#313131"</span>, <span class="comment">// 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。</span></span><br><span class="line">  <span class="string">"icons"</span>: [ <span class="comment">// 桌面图标，是一个数组</span></span><br><span class="line">    &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/lowres.webp"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"48x48"</span>,  <span class="comment">// 以空格分隔的图片尺寸</span></span><br><span class="line">    <span class="string">"type"</span>: <span class="string">"image/webp"</span>  <span class="comment">// 帮助userAgent快速排除不支持的类型</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/lowres"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"48x48"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/hd_hi.ico"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"72x72 96x96 128x128 256x256"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/hd_hi.svg"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"72x72"</span></span><br><span class="line">  &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Manifest参考文档：<a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/Web/Manifest</a></p>
</blockquote>
<blockquote>
<p>可以打开网站<a href="https://developers.google.cn/web/showcase/2015/chrome-dev-summit" target="_blank" rel="noopener">https://developers.google.cn/web/showcase/2015/chrome-dev-summit</a>查看添加至主屏幕的动图。</p>
</blockquote>
<blockquote>
<p>如果用的是安卓手机，可以下载chrome浏览器自己操作看看</p>
</blockquote>
<h2 id="service-worker实现离线缓存"><a href="#service-worker实现离线缓存" class="headerlink" title="service worker实现离线缓存"></a>service worker实现离线缓存</h2><h3 id="什么是service-worker"><a href="#什么是service-worker" class="headerlink" title="什么是service worker"></a>什么是service worker</h3><p>Service Worker 是 Chrome 团队提出和力推的一个 WEB API，用于给 web 应用提供高级的可持续的后台处理能力。</p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/4032102925-5a2b5f6fec25a_articlex.png" alt></p>
<p>Service Workers 就像介于服务器和网页之间的拦截器，能够拦截进出的HTTP 请求，从而完全控制你的网站。</p>
<p><strong>最主要的特点</strong></p>
<ul>
<li>在页面中注册并安装成功后，运行于浏览器后台，不受页面刷新的影响，可以监听和截拦作用域范围内所有页面的 HTTP 请求。</li>
<li>网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)</li>
<li>运行于浏览器后台，可以控制打开的作用域范围下所有的页面请求</li>
<li>单独的作用域范围，单独的运行环境和执行线程</li>
<li>不能操作页面 DOM。但可以通过事件机制来处理</li>
<li>事件驱动型服务线程</li>
</ul>
<blockquote>
<p>为什么要求网站必须是HTTPS的，大概是因为service worker权限太大能拦截所有页面的请求吧，如果http的网站安装service worker很容易被攻击</p>
</blockquote>
<p><strong>浏览器支持情况</strong></p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/20180928083222.png" alt></p>
<blockquote>
<p>浏览器支持情况详见： <a href="https://caniuse.com/#feat=serviceworkers" target="_blank" rel="noopener">https://caniuse.com/#feat=serviceworkers</a></p>
</blockquote>
<p><strong>生命周期</strong></p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/1448556198-5a2b5f702c485_articlex.png" alt></p>
<p>当用户首次导航至 URL 时，服务器会返回响应的网页。</p>
<ul>
<li>第1步:当你调用 register() 函数时， Service Worker 开始下载。</li>
<li>第2步:在注册过程中，浏览器会下载、解析并执行 Service Worker ()。如果在此步骤中出现任何错误，register() 返回的 promise 都会执行 reject 操作，并且 Service Worker 会被废弃。</li>
<li>第3步:一旦 Service Worker 成功执行了，install 事件就会激活</li>
<li>第4步:安装完成，Service Worker 便会激活，并控制在其范围内的一切。如果生命周期中的所有事件都成功了，Service Worker 便已准备就绪，随时可以使用了！</li>
</ul>
<blockquote>
<p><a href="chrome://serviceworker-internals" target="_blank" rel="noopener">chrome://serviceworker-internals</a> 来了解当前浏览器中所有已安装Service Worker的详细情况</p>
</blockquote>
<h3 id="HTTP缓存与service-worker缓存"><a href="#HTTP缓存与service-worker缓存" class="headerlink" title="HTTP缓存与service worker缓存"></a>HTTP缓存与service worker缓存</h3><ul>
<li>HTTP缓存</li>
</ul>
<p>Web 服务器可以使用 Expires 首部来通知 Web 客户端，它可以使用资源的当前副本，直到指定的“过期时间”。反过来，浏览器可以缓存此资源，并且只有在有效期满后才会再次检查新版本。<br>使用 HTTP 缓存意味着你要依赖服务器来告诉你何时缓存资源和何时过期。</p>
<ul>
<li>service worker缓存</li>
</ul>
<p>Service Workers 的强大在于它们拦截 HTTP 请求的能力<br>进入任何传入的 HTTP 请求，并决定想要如何响应。在你的 Service Worker 中，可以编写逻辑来决定想要缓存的资源，以及需要满足什么条件和资源需要缓存多久。一切尽归你掌控*</p>
<h3 id="实现离线缓存"><a href="#实现离线缓存" class="headerlink" title="实现离线缓存"></a>实现离线缓存</h3><p>index.html</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Hello Caching World!<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Image --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">"/images/hello.png"</span> /&gt;</span>                 </span><br><span class="line">    <span class="comment">&lt;!-- JavaScript --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">async</span> <span class="attr">src</span>=<span class="string">"/js/script.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span>     </span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 注册 service worker</span></span></span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> navigator) &#123;           </span></span><br><span class="line"><span class="actionscript">        navigator.serviceWorker.register(<span class="string">'/service-worker.js'</span>, &#123;scope: <span class="string">'/'</span>&#125;).then(<span class="function"><span class="keyword">function</span> <span class="params">(registration)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 注册成功</span></span></span><br><span class="line"><span class="javascript">          <span class="built_in">console</span>.log(<span class="string">'ServiceWorker registration successful with scope: '</span>, registration.scope);</span></span><br><span class="line"><span class="actionscript">        &#125;).catch(<span class="function"><span class="keyword">function</span> <span class="params">(err)</span> </span>&#123;                   </span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 注册失败 :(</span></span></span><br><span class="line"><span class="javascript">          <span class="built_in">console</span>.log(<span class="string">'ServiceWorker registration failed: '</span>, err);</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>注：Service Worker 的注册路径决定了其 scope 默认作用页面的范围。<br>如果 service-worker.js 是在 /sw/ 页面路径下，这使得该 Service Worker 默认只会收到 页面/sw/ 路径下的 fetch 事件。<br>如果存放在网站的根路径下，则将会收到该网站的所有 fetch 事件。<br>如果希望改变它的作用域，可在第二个参数设置 scope 范围。示例中将其改为了根目录，即对整个站点生效。</p>
</blockquote>
<p>service-worker.js</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cacheName = <span class="string">'helloWorld'</span>;     <span class="comment">// 缓存的名称  </span></span><br><span class="line"><span class="comment">// install 事件，它发生在浏览器安装并注册 Service Worker 时        </span></span><br><span class="line"><span class="keyword">self</span>.addEventListener(<span class="string">'install'</span>, event =&gt; &#123; </span><br><span class="line"><span class="comment">/* event.waitUtil 用于在安装成功之前执行一些预装逻辑</span></span><br><span class="line"><span class="comment"> 但是建议只做一些轻量级和非常重要资源的缓存，减少安装失败的概率</span></span><br><span class="line"><span class="comment"> 安装成功后 ServiceWorker 状态会从 installing 变为 installed */</span></span><br><span class="line">  event.waitUntil(</span><br><span class="line">    caches.open(cacheName)                  </span><br><span class="line">    .then(cache =&gt; cache.addAll([    <span class="comment">// 如果所有的文件都成功缓存了，便会安装完成。如果任何文件下载失败了，那么安装过程也会随之失败。        </span></span><br><span class="line">      <span class="string">'/js/script.js'</span>,</span><br><span class="line">      <span class="string">'/images/hello.png'</span></span><br><span class="line">    ]))</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br><span class="line">  </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">为 fetch 事件添加一个事件监听器。接下来，使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话，返回缓存的资源。</span></span><br><span class="line"><span class="comment">如果资源并不存在于缓存当中，通过网络来获取资源，并将获取到的资源添加到缓存中。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">self</span>.addEventListener(<span class="string">'fetch'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(event)</span> </span>&#123;</span><br><span class="line">  event.respondWith(</span><br><span class="line">    caches.match(event.request)                  </span><br><span class="line">    .then(<span class="function"><span class="keyword">function</span> <span class="params">(response)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (response) &#123;                            </span><br><span class="line">        <span class="keyword">return</span> response;                         </span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">var</span> requestToCache = event.request.<span class="keyword">clone</span>();  <span class="comment">//          </span></span><br><span class="line">      <span class="keyword">return</span> fetch(requestToCache).then(                   </span><br><span class="line">        <span class="function"><span class="keyword">function</span> <span class="params">(response)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (!response || response.status !== <span class="number">200</span>) &#123;      </span><br><span class="line">            <span class="keyword">return</span> response;</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">var</span> responseToCache = response.<span class="keyword">clone</span>();          </span><br><span class="line">          caches.open(cacheName)                           </span><br><span class="line">            .then(<span class="function"><span class="keyword">function</span> <span class="params">(cache)</span> </span>&#123;</span><br><span class="line">              cache.put(requestToCache, responseToCache);  </span><br><span class="line">            &#125;);</span><br><span class="line">          <span class="keyword">return</span> response;             </span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注：为什么用request.clone()和response.clone()<br>需要这么做是因为request和response是一个流，它只能消耗一次。因为我们已经通过缓存消耗了一次，然后发起 HTTP 请求还要再消耗一次，所以我们需要在此时克隆请求<br>Clone the request—a request is a stream and can only be consumed once.</p>
</blockquote>
<h3 id="调试相关"><a href="#调试相关" class="headerlink" title="调试相关"></a>调试相关</h3><p>chrome浏览器打开<a href="https://googlechrome.github.io/samples/service-worker/basic/index.html" target="_blank" rel="noopener">https://googlechrome.github.io/samples/service-worker/basic/index.html</a>，这是一个实现了service worker离线缓存功能的网站，打开调试工具</p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/2894994167-5a2b5f703e61d_articlex.png" alt></p>
<p>介绍一个图中的1.和2.</p>
<ol>
<li>勾选可以模拟网站离线情况，勾选后network会有一个黄色警告图标，该网站已经离线。此时刷新页面，页面仍然能够正常显示</li>
<li>当前service worker的scope。它能够拦截<a href="https://googlechrome.github.i...，同样也能够拦截https://googlechrome.github.i...*/*.html下的请求" target="_blank" rel="noopener">https://googlechrome.github.i...，同样也能够拦截https://googlechrome.github.i...*/*.html下的请求</a></li>
</ol>
<blockquote>
<p>调试面板具体代表的什么参看<a href="https://x5.tencent.com/tbs/guide/serviceworker.html" target="_blank" rel="noopener">https://x5.tencent.com/tbs/guide/serviceworker.html</a>的第三部分</p>
</blockquote>
<h3 id="serice-worker实现消息推送"><a href="#serice-worker实现消息推送" class="headerlink" title="serice worker实现消息推送"></a>serice worker实现消息推送</h3><p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/2113064281-5a2b5f700af67_articlex.png" alt></p>
<ul>
<li>步骤一、提示用户并获得他们的订阅详细信息</li>
<li>步骤二、将这些详细信息保存在服务器上</li>
<li>步骤三、在需要时发送任何消息</li>
</ul>
<blockquote>
<p>不同浏览器需要用不同的推送消息服务器。以 Chrome 上使用 Google Cloud Messaging<gcm> 作为推送服务为例，第一步是注册 applicationServerKey(通过 GCM 注册获取)，并在页面上进行订阅或发起订阅。每一个会话会有一个独立的端点（endpoint），订阅对象的属性(PushSubscription.endpoint) 即为端点值。将端点发送给服务器后，服务器用这一值来发送消息给会话的激活的 Service Worker （通过 GCM 与浏览器客户端沟通）。</gcm></p>
</blockquote>
<p><strong>步骤一和步骤二</strong></p>
<p>index.html</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Progressive Times<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"manifest"</span> <span class="attr">href</span>=<span class="string">"/manifest.json"</span>&gt;</span>                                      </span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> endpoint;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> key;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> authSecret;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> vapidPublicKey = <span class="string">'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'</span>;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 方法很复杂，但是可以不用具体看，知识用来转化vapidPublicKey用</span></span></span><br><span class="line"><span class="actionscript">      <span class="function"><span class="keyword">function</span> <span class="title">urlBase64ToUint8Array</span><span class="params">(base64String)</span> </span>&#123;                                  </span></span><br><span class="line"><span class="actionscript">        <span class="keyword">const</span> padding = <span class="string">'='</span>.repeat((<span class="number">4</span> - base64String.length % <span class="number">4</span>) % <span class="number">4</span>);</span></span><br><span class="line"><span class="actionscript">        <span class="keyword">const</span> base64 = (base64String + padding)</span></span><br><span class="line"><span class="javascript">          .replace(<span class="regexp">/\-/g</span>, <span class="string">'+'</span>)</span></span><br><span class="line"><span class="javascript">          .replace(<span class="regexp">/_/g</span>, <span class="string">'/'</span>);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> rawData = <span class="built_in">window</span>.atob(base64);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> outputArray = <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(rawData.length);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; rawData.length; ++i) &#123;</span></span><br><span class="line">          outputArray[i] = rawData.charCodeAt(i);</span><br><span class="line">        &#125;</span><br><span class="line"><span class="actionscript">        <span class="keyword">return</span> outputArray;</span></span><br><span class="line">      &#125;</span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> navigator) &#123;</span></span><br><span class="line"><span class="actionscript">        navigator.serviceWorker.register(<span class="string">'sw.js'</span>).then(<span class="function"><span class="keyword">function</span> <span class="params">(registration)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">          <span class="keyword">return</span> registration.pushManager.getSubscription()                            </span></span><br><span class="line"><span class="actionscript">            .then(<span class="function"><span class="keyword">function</span> <span class="params">(subscription)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">              <span class="keyword">if</span> (subscription) &#123;                                                      </span></span><br><span class="line"><span class="actionscript">                <span class="keyword">return</span>;</span></span><br><span class="line">              &#125;</span><br><span class="line"><span class="actionscript">              <span class="keyword">return</span> registration.pushManager.subscribe(&#123;                              </span></span><br><span class="line"><span class="actionscript">                  userVisibleOnly: <span class="literal">true</span>,</span></span><br><span class="line">                  applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)</span><br><span class="line">                &#125;)</span><br><span class="line"><span class="actionscript">                .then(<span class="function"><span class="keyword">function</span> <span class="params">(subscription)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">                  <span class="keyword">var</span> rawKey = subscription.getKey ? subscription.getKey(<span class="string">'p256dh'</span>) : <span class="string">''</span>;</span></span><br><span class="line"><span class="javascript">                  key = rawKey ? btoa(<span class="built_in">String</span>.fromCharCode.apply(<span class="literal">null</span>, <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(rawKey))) : <span class="string">''</span>;</span></span><br><span class="line"><span class="actionscript">                  <span class="keyword">var</span> rawAuthSecret = subscription.getKey ? subscription.getKey(<span class="string">'auth'</span>) : <span class="string">''</span>;</span></span><br><span class="line">                  authSecret = rawAuthSecret ?</span><br><span class="line"><span class="javascript">                    btoa(<span class="built_in">String</span>.fromCharCode.apply(<span class="literal">null</span>, <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(rawAuthSecret))) : <span class="string">''</span>;</span></span><br><span class="line">                  endpoint = subscription.endpoint;</span><br><span class="line"><span class="actionscript">                  <span class="keyword">return</span> fetch(<span class="string">'./register'</span>, &#123;                                         </span></span><br><span class="line"><span class="actionscript">                    method: <span class="string">'post'</span>,</span></span><br><span class="line"><span class="actionscript">                    headers: <span class="keyword">new</span> Headers(&#123;</span></span><br><span class="line"><span class="actionscript">                      <span class="string">'content-type'</span>: <span class="string">'application/json'</span></span></span><br><span class="line">                    &#125;),</span><br><span class="line"><span class="javascript">                    body: <span class="built_in">JSON</span>.stringify(&#123;</span></span><br><span class="line">                      endpoint: subscription.endpoint,</span><br><span class="line">                      key: key,</span><br><span class="line">                      authSecret: authSecret,</span><br><span class="line">                    &#125;),</span><br><span class="line">                  &#125;);</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;);</span><br><span class="line"><span class="actionscript">        &#125;).catch(<span class="function"><span class="keyword">function</span> <span class="params">(err)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 注册失败 :(</span></span></span><br><span class="line"><span class="javascript">          <span class="built_in">console</span>.log(<span class="string">'ServiceWorker registration failed: '</span>, err);</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p><strong>步骤三 服务器发送消息给service worker</strong></p>
<p>app.js</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> webpush = <span class="built_in">require</span>(<span class="string">'web-push'</span>);                 </span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">var</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>);</span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line">webpush.setVapidDetails(                             </span><br><span class="line">  <span class="string">'mailto:contact@deanhume.com'</span>,</span><br><span class="line">  <span class="string">'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'</span>,</span><br><span class="line">  <span class="string">'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'</span></span><br><span class="line">);</span><br><span class="line">app.post(<span class="string">'/register'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;           </span><br><span class="line">  <span class="keyword">var</span> endpoint = req.body.endpoint;</span><br><span class="line">  saveRegistrationDetails(endpoint, key, authSecret); </span><br><span class="line">  <span class="keyword">const</span> pushSubscription = &#123;                          </span><br><span class="line">    endpoint: req.body.endpoint,</span><br><span class="line">    keys: &#123;</span><br><span class="line">      auth: req.body.authSecret,</span><br><span class="line">      p256dh: req.body.key</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">var</span> body = <span class="string">'Thank you for registering'</span>;</span><br><span class="line">  <span class="keyword">var</span> iconUrl = <span class="string">'https://example.com/images/homescreen.png'</span>;</span><br><span class="line">  <span class="comment">// 发送 Web 推送消息</span></span><br><span class="line">  webpush.sendNotification(pushSubscription,          </span><br><span class="line">      <span class="built_in">JSON</span>.stringify(&#123;</span><br><span class="line">        msg: body,</span><br><span class="line">        url: <span class="string">'http://localhost:3111/'</span>,</span><br><span class="line">        icon: iconUrl</span><br><span class="line">      &#125;))</span><br><span class="line">    .then(<span class="function"><span class="params">result</span> =&gt;</span> res.sendStatus(<span class="number">201</span>))</span><br><span class="line">    .catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(err);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="number">3111</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'Web push app listening on port 3111!'</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p><strong>service worker监听push事件，将通知详情推送给用户</strong></p>
<p>service-worker.js</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">self</span>.addEventListener(<span class="string">'push'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(event)</span> </span>&#123;</span><br><span class="line"> <span class="comment">// 检查服务端是否发来了任何有效载荷数据</span></span><br><span class="line">  <span class="keyword">var</span> payload = event.data ? JSON.parse(event.data.text()) : <span class="string">'no payload'</span>;</span><br><span class="line">  <span class="keyword">var</span> title = <span class="string">'Progressive Times'</span>;</span><br><span class="line">  event.waitUntil(</span><br><span class="line">    <span class="comment">// 使用提供的信息来显示 Web 推送通知</span></span><br><span class="line">    <span class="keyword">self</span>.registration.showNotification(title, &#123;                           </span><br><span class="line">      body: payload.msg,</span><br><span class="line">      url: payload.url,</span><br><span class="line">      icon: payload.icon</span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>扩展知识：<a href="https://lzw.me/a/pwa-service-worker.html#3.3" target="_blank" rel="noopener">service worker的更新</a></p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><h2 id="PWA的优势"><a href="#PWA的优势" class="headerlink" title="PWA的优势"></a>PWA的优势</h2><ul>
<li>可以将app的快捷方式放置到桌面上，全屏运行，与原生app无异</li>
<li>能够在各种网络环境下使用，包括网络差和断网条件下，不会显示undefind</li>
<li>推送消息的能力</li>
<li>其本质是一个网页，没有原生app的各种启动条件，快速响应用户指令</li>
</ul>
<h2 id="PWA存在的问题"><a href="#PWA存在的问题" class="headerlink" title="PWA存在的问题"></a>PWA存在的问题</h2><ul>
<li>依赖的GCM服务在国内无法使用</li>
<li>微信小程序的竞争</li>
</ul>
<p>尽管有上述的一些缺点，PWA技术仍然有很多可以使用的点。</p>
<ul>
<li>service worker技术实现离线缓存，可以将一些不经常更改的静态文件放到缓存中，提升用户体验。</li>
<li>service worker实现消息推送，使用浏览器推送功能，吸引用户</li>
<li>渐进式开发，尽管一些浏览器暂时不支持，可以利用上述技术给使用支持浏览器的用户带来更好的体验。</li>
</ul>
<h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><ul>
<li><a href="https://github.com/SangKa/PWA-Book-CN" target="_blank" rel="noopener">第一本 PWA 中文书</a></li>
<li><a href="https://github.com/deanhume/progressive-web-apps-book" target="_blank" rel="noopener">PWA 英文书</a></li>
<li><a href="https://lzw.me/a/pwa-service-worker.html" target="_blank" rel="noopener">网站渐进式增强体验(PWA)改造：Service Worker 应用详解</a></li>
<li><a href="https://googlechrome.github.io/samples/service-worker/basic/index.html" target="_blank" rel="noopener">Basic Service Worker Sample</a></li>
<li><a href="https://www.w3ctech.com/topic/866" target="_blank" rel="noopener">【翻译】Service Worker 入门</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" target="_blank" rel="noopener">Web App Manifest</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" target="_blank" rel="noopener">Service Workers: an Introduction</a></li>
<li><a href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/" target="_blank" rel="noopener">The Offline Cookbook</a></li>
<li><a href="https://blog.csdn.net/baidu_browser/article/details/64440238" target="_blank" rel="noopener">微信小程序和PWA对比分析</a></li>
<li><a href="https://x5.tencent.com/tbs/guide/serviceworker.html" target="_blank" rel="noopener">Service Worker最佳实践</a></li>
</ul>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/27/【PWA学习与实践】-1-2018，开始你的PWA学习之旅/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/27/【PWA学习与实践】-1-2018，开始你的PWA学习之旅/" itemprop="url">【PWA学习与实践】(1) 2018，开始你的PWA学习之旅</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-27T20:02:04+08:00">
                2018-09-27
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/pwa/" itemprop="url" rel="index">
                    <span itemprop="name">pwa</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p><img src="/blog/2018/09/27/【PWA学习与实践】-1-2018，开始你的PWA学习之旅/6476654-989b2f04aa546a5f.png" alt></p>
<p>PWA作为今年最火热的技术概念之一，对提升Web应用的安全、性能和体验有着很大的意义，非常值得我们去了解与学习。</p>
<p>本系列文章《PWA技术学习与实践》会逐步拆解PWA背后的各项技术，通过实例代码来讲解这些技术的应用方式。也正是因为PWA中技术点众多、知识细碎，因此我在学习过程中，进行了整理，并产出了《PWA技术学习与实践》系列文章，希望能带大家全面了解PWA中的各项技术。对PWA感兴趣的朋友欢迎关注。</p>
<p>首先简单了解一下PWA。</p>
<h1 id="什么是PWA"><a href="#什么是PWA" class="headerlink" title="什么是PWA"></a>什么是PWA</h1><blockquote>
<p>PWA，即Progressive Web App, 是提升 Web App 的体验的一种新方法，能给用户原生应用的体验。</p>
</blockquote>
<p>我们需要理解的是，PWA不是某一项技术，或者某一个新的产物；而是一系列Web技术与标准的集合与应用。通过应用这些新的技术与标准，可以从安全、性能和体验三个方面，优化我们的Web App。所以，其实PWA本质上依然是一个Web App。</p>
<p>因此，学习PWA其实就是了解与掌握这些PWA背后的技术。本系列文章会针对PWA中所涉及到的技术进行介绍，并配合代码实例来展示各类技术的使用方式。希望通过这一系列文章，让大家对PWA技术有一个更深入的认识。而PWA本身渐进式的思想也可以让我们在业务中“渐进式”地使用这些技术，在成本可控的前提下，不断优化我们的产品。</p>
<h1 id="PWA中的一些技术"><a href="#PWA中的一些技术" class="headerlink" title="PWA中的一些技术"></a>PWA中的一些技术</h1><p>PWA本身其实是一个概念集合，它不是指某一项技术，而是通过一系列的Web技术与Web标准来优化Web App的安全、性能和体验。其中涉及到的一些技术概念包括了：</p>
<ul>
<li>Web App Manifest</li>
<li>Service Worker</li>
<li>Cache API 缓存</li>
<li>Push&amp;Notification 推送与通知</li>
<li>Background Sync 后台同步</li>
<li>响应式设计</li>
<li>……</li>
</ul>
<p>这些技术都是你在学习PWA中不可或缺的。而随着apple在iOS Safari中也开始支持PWA（其中的某些技术），PWA的舞台更大了。</p>
<h1 id="项目DEMO"><a href="#项目DEMO" class="headerlink" title="项目DEMO"></a>项目DEMO</h1><p>为了配合PWA中相关知识的学习，我专门创建了一个demo Web App——</p>
<p>一个根据关键字查询图书信息的demo（<a href="https://github.com/alienzhou/learning-pwa）。" target="_blank" rel="noopener">https://github.com/alienzhou/learning-pwa）。</a></p>
<p>这个Web App最开始是不具备任何PWA的能力。我会在这一系列文章中以这个demo为例，阐述各项技术的同时，将其应用在demo上。也就是说，在这一系列的文章中，我会和大家一起将一个普通的网页应用逐步升级为一个简单的PWA，通过这种方式一起学习。</p>
<p>首先简单介绍一下这个demo。这是一个根据关键词搜索图书信息的应用，用户在前端输入关键词，点击搜索，会请求我们自己的服务器，而服务器使用豆瓣图书API V2来获取数据。</p>
<p><img src="/blog/2018/09/27/【PWA学习与实践】-1-2018，开始你的PWA学习之旅/6476654-61e8e1ee38c99d84.jpg" alt></p>
<p>项目使用KOA来搭建node服务器，所以需要node版本&gt;7.6.0，可以使用nvm来切换到适合的node版本。</p>
<p>要运行该项目，首先</p>
<figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="keyword">clone</span> <span class="title">git</span>@github.com:alienzhou/learning-pwa.git</span><br><span class="line"><span class="comment"># 切换到基础项目分支</span></span><br><span class="line">git checkout basic</span><br></pre></td></tr></table></figure>
<p>注意，需要切换到basic分支，master分支是上经过PWA升级后最新的demo代码。只有在basic分支才能看到原始的Web App。接下来，安装依赖：</p>
<p><code>npm install</code></p>
<p>最后，运行项目：</p>
<p><code>npm run start</code></p>
<p>然后就可以在127.0.0.1:8085上访问到该项目。</p>
<p>基础demo的代码比较简单，这里就不去赘述demo中的代码细节了。简单了解一下项目结构，前端代码都存放于public目录中，具体结构如下：</p>
<figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">|<span class="params">---public---</span>|<span class="params">---index</span>.html <span class="string">//</span> 前端页面 </span><br><span class="line">| |<span class="params">---index</span>.js <span class="string">//</span> browser的JavaScript脚本 </span><br><span class="line">| |<span class="params">---style</span>.css <span class="string">//</span> 样式文件 </span><br><span class="line">| |<span class="params">---img</span> <span class="string">//</span> 图片文件夹 </span><br><span class="line">|<span class="params">---app</span>.js <span class="string">//</span> node服务启动入口 </span><br><span class="line">|<span class="params">---util</span>.js <span class="string">//</span> node服务工具库</span><br></pre></td></tr></table></figure>
<p>值得一提的是，后续文章内的代码会以分支的形式存在，每篇文章的最终代码会存放于一个对应的分支中。你可以通过方便得切换分支，来查看每篇文章对应的示例代码。</p>
<ul>
<li>basic分支：基础项目demo，一个普通的图书搜索应用（网站）；</li>
<li>manifest分支：基于basic分支，添加manifest等功能；</li>
<li>sw-cache分支：基于manifest分支，添加缓存与离线功能；</li>
<li>master分支：应用的最新代码。</li>
<li>……</li>
</ul>
<h1 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h1><p>作为本系列的第一篇文章，本文简单介绍了PWA与其相关的技术概念。通过学习PWA，我们可以很快将其中的优秀技术应用到我们的工作里。在下一篇文章中，我就会介绍如何使用manifest来让你的Web App“更Native”，拥有一个App Shell。想了解PWA更多的后续知识，欢迎关注我，关注《PWA技术学习与实践》系列文章。</p>
<p>话不多说，下面就让我们来具体地学习PWA相关技术吧！</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/27/javascript-单例模式/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/27/javascript-单例模式/" itemprop="url">javascript 单例模式</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-27T13:04:22+08:00">
                2018-09-27
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/javascript/" itemprop="url" rel="index">
                    <span itemprop="name">javascript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p><strong>单例模式的定义是：保证一个类仅有一个实例，并提供一个访问它的全局访问点。</strong></p>
<p>单例模式能在合适的时候创建对象，并且创建唯一的一个。<br>代码接近于生活，很有意思。比如一个网站的登录，点击登录后弹出一个登录弹框，即使再次点击，也不会再出现一个相同的弹框。又或者一个音乐播放程序，如果用户打开了一个音乐，又想打开一个音乐，那么之前的播放界面就会自动关闭，切换到当前的播放界面。这些都是单例模式的应用场景。<br>要实现一个单例模式，一个经典的方式是创建一个类，类中又一个方法能创建该类的实例对象，还有一个标记，记录是否已经创了过了实例对象。如果对象已经存在，就返回第一次实例化对象的引用。</p>
<h1 id="单例模式的实现"><a href="#单例模式的实现" class="headerlink" title="单例模式的实现"></a>单例模式的实现</h1><h2 id="es5实现方式"><a href="#es5实现方式" class="headerlink" title="es5实现方式"></a>es5实现方式</h2><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Singleton = <span class="function"><span class="keyword">function</span><span class="params">(name)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = name;</span><br><span class="line">    <span class="comment">//一个标记，用来判断是否已将创建了该类的实例</span></span><br><span class="line">    <span class="keyword">this</span>.instance = <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 提供了一个静态方法，用户可以直接在类上调用</span></span><br><span class="line">Singleton.getInstance = <span class="function"><span class="keyword">function</span><span class="params">(name)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 没有实例化的时候创建一个该类的实例</span></span><br><span class="line">    <span class="keyword">if</span>(!<span class="keyword">this</span>.instance) &#123;</span><br><span class="line">        <span class="keyword">this</span>.instance = <span class="keyword">new</span> Singleton(name);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 已经实例化了，返回第一次实例化对象的引用</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.instance;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>用户可以通过一个广为人知的接口，对该实例进行访问。<br>我们尝试对该对象进行两次实例化，观察两次实例化结果是否指向同一个对象。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = Singleton.getInstance(<span class="string">'sven1'</span>);</span><br><span class="line"><span class="keyword">var</span> b = Singleton.getInstance(<span class="string">'sven2'</span>);</span><br><span class="line"><span class="comment">// 指向的是唯一实例化的对象</span></span><br><span class="line"><span class="built_in">console</span>.log(a === b);</span><br></pre></td></tr></table></figure>
<p>返回结果是：true。说明a、b之间是引用关系。</p>
<h2 id="es6实现方式"><a href="#es6实现方式" class="headerlink" title="es6实现方式"></a>es6实现方式</h2><p>创建Singleton类。class关键字和静态函数都是es6新增的。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Singleton</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span>(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">        <span class="keyword">this</span>.instance = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 构造一个广为人知的接口，供用户对该类进行实例化</span></span><br><span class="line">    static getInstance(name) &#123;</span><br><span class="line">        <span class="keyword">if</span>(!<span class="keyword">this</span>.instance) &#123;</span><br><span class="line">            <span class="keyword">this</span>.instance = new Singleton(name);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="typescript实现方式"><a href="#typescript实现方式" class="headerlink" title="typescript实现方式"></a>typescript实现方式</h2><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title">Singleton</span> &#123;</span><br><span class="line">     </span><br><span class="line">  <span class="function"><span class="keyword">private</span> <span class="title">constructor</span> (<span class="params">name: <span class="keyword">string</span></span>)</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.name = name</span><br><span class="line">   </span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">static</span> instance: Singleton</span><br><span class="line">   <span class="keyword">public</span> name: <span class="keyword">string</span>  </span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">  <span class="function"><span class="keyword">static</span> <span class="title">getInstance</span> (<span class="params">name: <span class="keyword">string</span></span>): Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>.instance) &#123;</span><br><span class="line">      <span class="keyword">this</span>.instance = <span class="keyword">new</span> Singleton(name)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.instance</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="单例模式应用实例"><a href="#单例模式应用实例" class="headerlink" title="单例模式应用实例"></a>单例模式应用实例</h1><p>我们用一个生活中常见的一个场景来说明单例模式的应用。<br>任意一个网站，点击登录按钮，只会弹出有且仅有一个登录框，即使后面再点击登录按钮，也不会再弹出多一个弹框。这就是单例模式的典型应用。接下来我们实现它。为了注重单例模式的展示，我们把登录框简化吧😜</p>
<p>在页面上设置一个按钮</p>
<p><code>&lt;button id=&quot;loginBtn&quot;&gt;登录&lt;/button&gt;</code></p>
<p>为这个按钮添加点击事件</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> btn = <span class="built_in">document</span>.getElementById(<span class="string">'loginBtn'</span>);</span><br><span class="line">btn.addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    loginLayer.getInstance();</span><br><span class="line">&#125;, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure>
<p>我们先给一个初始化方法init，在点击按钮之后，在页面上添加一个框框（权当登录框了）</p>
<figure class="highlight cal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">init() &#123;</span><br><span class="line">    <span class="keyword">var</span> <span class="keyword">div</span> = document.createElement(<span class="string">'div'</span>);</span><br><span class="line">    <span class="keyword">div</span>.classList.add(<span class="string">'login-layer'</span>);</span><br><span class="line">    <span class="keyword">div</span>.innerHTML = <span class="string">"我是登录浮窗"</span>;</span><br><span class="line">    document.body.appendChild(<span class="keyword">div</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>那么接下来要用单例模式实现，点击按钮出现有且仅有一个浮窗，方法就是在构造函数中创建一个标记，第一次点击时创建一个浮窗，用改变标记的状态，再次点击按钮时，根据标记的状态判断是否要再创建一个浮窗。<br>上源码：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">loginLayer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span>() &#123;</span><br><span class="line">        <span class="comment">// 判断页面是否已有浮窗的标记</span></span><br><span class="line">        <span class="keyword">this</span>.instance = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">this</span>.<span class="keyword">init</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">init</span>() &#123;</span><br><span class="line">        <span class="keyword">var</span> div = document.createElement(<span class="string">'div'</span>);</span><br><span class="line">        div.classList.add(<span class="string">'login-layer'</span>);</span><br><span class="line">        div.innerHTML = <span class="string">"我是登录浮窗"</span>;</span><br><span class="line">        document.body.appendChild(div);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 静态方法作为广为人知的接口</span></span><br><span class="line">    static getInstance() &#123;</span><br><span class="line">        <span class="comment">// 根据标记的状态判断是否要再添加浮窗，如果标记不为空就创建一个浮窗</span></span><br><span class="line">        <span class="keyword">if</span>(!<span class="keyword">this</span>.instance) &#123;</span><br><span class="line">            <span class="keyword">this</span>.instance = new loginLayer();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>当然不要忘记为浮窗添加一个样式，让浮窗显示啦</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.login-layer</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">200px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">200px</span>;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="built_in">rgba</span>(0, 0, 0, .6);</span><br><span class="line">    <span class="attribute">text-align</span>: center;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">200px</span>;</span><br><span class="line">    <span class="attribute">display</span>: inline-block;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最后奉上该实例的源码：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">   loginLayer.getInstance();</span><br><span class="line">        &#125;, <span class="literal">false</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="class"><span class="keyword">class</span> <span class="title">loginLayer</span> </span>&#123;</span><br><span class="line">            <span class="keyword">constructor</span>() &#123;</span><br><span class="line">                <span class="keyword">this</span>.instance = <span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">this</span>.<span class="keyword">init</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">init</span>() &#123;</span><br><span class="line">                <span class="keyword">var</span> div = document.createElement(<span class="string">'div'</span>);</span><br><span class="line">                div.classList.add(<span class="string">'login-layer'</span>);</span><br><span class="line">                div.innerHTML = <span class="string">"我是登录浮窗"</span>;</span><br><span class="line">                document.body.appendChild(div);</span><br><span class="line">            &#125;</span><br><span class="line">            static getInstance() &#123;</span><br><span class="line">                <span class="keyword">if</span>(!<span class="keyword">this</span>.instance) &#123;</span><br><span class="line">                    <span class="keyword">this</span>.instance = new loginLayer();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.instance;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/27/TypeScript-进阶篇/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/27/TypeScript-进阶篇/" itemprop="url">TypeScript 进阶篇</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-27T10:31:37+08:00">
                2018-09-27
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/typescript/" itemprop="url" rel="index">
                    <span itemprop="name">typescript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="类型别名"><a href="#类型别名" class="headerlink" title="类型别名"></a>类型别名</h1><p>类型别名用来给一个类型起个新名字。</p>
<h2 id="简单的例子"><a href="#简单的例子" class="headerlink" title="简单的例子"></a>简单的例子</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Name = <span class="built_in">string</span>;</span><br><span class="line"><span class="keyword">type</span> NameResolver = <span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">string</span>;</span><br><span class="line"><span class="keyword">type</span> NameOrResolver = Name | NameResolver;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getName</span>(<span class="params">n: NameOrResolver</span>): <span class="title">Name</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> n === <span class="string">'string'</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> n;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> n();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上例中，我们使用 type 创建类型别名。</p>
<p>类型别名常用于联合类型。</p>
<h1 id="字符串字面量类型"><a href="#字符串字面量类型" class="headerlink" title="字符串字面量类型"></a>字符串字面量类型</h1><p>字符串字面量类型用来约束取值只能是某几个字符串中的一个。</p>
<h2 id="简单的例子-1"><a href="#简单的例子-1" class="headerlink" title="简单的例子"></a>简单的例子</h2><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">type</span> EventNames = <span class="string">'click'</span> | <span class="string">'scroll'</span> | <span class="string">'mousemove'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleEvent</span><span class="params">(ele: Element, event: EventNames)</span></span> &#123;</span><br><span class="line">    // <span class="keyword">do</span> something</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">handleEvent(document.getElementById(<span class="string">'hello'</span>), <span class="string">'scroll'</span>);  // 没问题</span><br><span class="line">handleEvent(document.getElementById(<span class="string">'world'</span>), <span class="string">'dbclick'</span>); // 报错，event 不能为 <span class="string">'dbclick'</span></span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">7</span>,<span class="number">47</span>): <span class="built_in">error</span> TS2345: Argument of <span class="built_in">type</span> <span class="string">'"dbclick"'</span> is <span class="keyword">not</span> assignable to parameter of <span class="built_in">type</span> <span class="string">'EventNames'</span>.</span><br></pre></td></tr></table></figure>
<p>上例中，我们使用 type 定了一个字符串字面量类型 EventNames，它只能取三种字符串中的一种。</p>
<p>注意，类型别名与字符串字面量类型都是使用 type 进行定义。</p>
<h1 id="元组"><a href="#元组" class="headerlink" title="元组"></a>元组</h1><p>数组合并了相同类型的对象，而元组（Tuple）合并了不同类型的对象。</p>
<p>元组起源于函数编程语言（如 F#）,在这些语言中频繁使用元组。</p>
<h2 id="简单的例子-2"><a href="#简单的例子-2" class="headerlink" title="简单的例子"></a>简单的例子</h2><p>定义一对值分别为 string 和 number 的元组：</p>
<p><code>let xcatliu: [string, number] = [&#39;Xcat Liu&#39;, 25];</code></p>
<p>当赋值或访问一个已知索引的元素时，会得到正确的类型：</p>
<figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">let xcatliu: [string, number];</span><br><span class="line">xcatliu[<span class="number">0</span>] = <span class="string">'Xcat Liu'</span>;</span><br><span class="line">xcatliu[<span class="number">1</span>] = <span class="number">25</span>;</span><br><span class="line"></span><br><span class="line">xcatliu[<span class="number">0</span>].slice(<span class="number">1</span>);</span><br><span class="line">xcatliu[<span class="number">1</span>].toFixed(<span class="number">2</span>);</span><br></pre></td></tr></table></figure>
<p>也可以只赋值其中一项：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> xcatliu: [<span class="built_in">string</span>, <span class="built_in">number</span>];</span><br><span class="line">xcatliu[<span class="number">0</span>] = <span class="string">'Xcat Liu'</span>;</span><br></pre></td></tr></table></figure>
<p>但是当直接对元组类型的变量进行初始化或者赋值的时候，需要提供所有元组类型中指定的项。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> xcatliu: [<span class="built_in">string</span>, <span class="built_in">number</span>];</span><br><span class="line">xcatliu = [<span class="string">'Xcat Liu'</span>, <span class="number">25</span>];</span><br></pre></td></tr></table></figure>
<figure class="highlight vhdl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">let xcatliu: [<span class="built_in">string</span>, number] = [<span class="symbol">'Xcat</span> Liu'];</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">1</span>,<span class="number">5</span>): <span class="literal">error</span> TS2322: <span class="keyword">Type</span> '[<span class="built_in">string</span>]' <span class="keyword">is</span> <span class="keyword">not</span> assignable <span class="keyword">to</span> <span class="keyword">type</span> '[<span class="built_in">string</span>, number]'.</span><br><span class="line">//   <span class="keyword">Property</span> <span class="string">'1'</span> <span class="keyword">is</span> missing <span class="keyword">in</span> <span class="keyword">type</span> '[<span class="built_in">string</span>]'.</span><br></pre></td></tr></table></figure>
<figure class="highlight vhdl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">let xcatliu: [<span class="built_in">string</span>, number];</span><br><span class="line">xcatliu = [<span class="symbol">'Xcat</span> Liu'];</span><br><span class="line">xcatliu[<span class="number">1</span>] = <span class="number">25</span>;</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">2</span>,<span class="number">1</span>): <span class="literal">error</span> TS2322: <span class="keyword">Type</span> '[<span class="built_in">string</span>]' <span class="keyword">is</span> <span class="keyword">not</span> assignable <span class="keyword">to</span> <span class="keyword">type</span> '[<span class="built_in">string</span>, number]'.</span><br><span class="line">//   <span class="keyword">Property</span> <span class="string">'1'</span> <span class="keyword">is</span> missing <span class="keyword">in</span> <span class="keyword">type</span> '[<span class="built_in">string</span>]'.</span><br></pre></td></tr></table></figure>
<h2 id="越界的元素"><a href="#越界的元素" class="headerlink" title="越界的元素"></a>越界的元素</h2><p>当添加越界的元素时，它的类型会被限制为元组中每个类型的联合类型：</p>
<figure class="highlight vhdl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">let xcatliu: [<span class="built_in">string</span>, number];</span><br><span class="line">xcatliu = [<span class="symbol">'Xcat</span> Liu', <span class="number">25</span>];</span><br><span class="line">xcatliu.push(<span class="symbol">'http</span>://xcatliu.com/');</span><br><span class="line">xcatliu.push(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">4</span>,<span class="number">14</span>): <span class="literal">error</span> TS2345: Argument <span class="keyword">of</span> <span class="keyword">type</span> <span class="symbol">'boolean</span>' <span class="keyword">is</span> <span class="keyword">not</span> assignable <span class="keyword">to</span> <span class="keyword">parameter</span> <span class="keyword">of</span> <span class="keyword">type</span> <span class="symbol">'string</span> | number'.</span><br><span class="line">//   <span class="keyword">Type</span> <span class="symbol">'boolean</span>' <span class="keyword">is</span> <span class="keyword">not</span> assignable <span class="keyword">to</span> <span class="keyword">type</span> <span class="symbol">'number</span>'.</span><br></pre></td></tr></table></figure>
<h1 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h1><p>枚举（Enum）类型用于取值被限定在一定范围内的场景，比如一周只能有七天，颜色限定为红绿蓝等。</p>
<h2 id="简单的例子-3"><a href="#简单的例子-3" class="headerlink" title="简单的例子"></a>简单的例子</h2><p>枚举使用 <strong>enum</strong> 关键字来定义：</p>
<p><code>enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};</code></p>
<p>枚举成员会被赋值为从 0 开始递增的数字，同时也会对枚举值到枚举名进行反向映射：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Days &#123;Sun, Mon, Tue, Wed, Thu, Fri, Sat&#125;;</span><br><span class="line"></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="string">"Sun"</span>] === <span class="number">0</span>); <span class="comment">// true</span></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="string">"Mon"</span>] === <span class="number">1</span>); <span class="comment">// true</span></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="string">"Tue"</span>] === <span class="number">2</span>); <span class="comment">// true</span></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="string">"Sat"</span>] === <span class="number">6</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="number">0</span>] === <span class="string">"Sun"</span>); <span class="comment">// true</span></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="number">1</span>] === <span class="string">"Mon"</span>); <span class="comment">// true</span></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="number">2</span>] === <span class="string">"Tue"</span>); <span class="comment">// true</span></span><br><span class="line">console.<span class="built_in">log</span>(Days[<span class="number">6</span>] === <span class="string">"Sat"</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>事实上，上面的例子会被编译为：</p>
<figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">var</span> <span class="built_in">Days</span>;</span><br><span class="line">(function (<span class="built_in">Days</span>) &#123;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Sun"</span>] = <span class="number">0</span>] = <span class="string">"Sun"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Mon"</span>] = <span class="number">1</span>] = <span class="string">"Mon"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Tue"</span>] = <span class="number">2</span>] = <span class="string">"Tue"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Wed"</span>] = <span class="number">3</span>] = <span class="string">"Wed"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Thu"</span>] = <span class="number">4</span>] = <span class="string">"Thu"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Fri"</span>] = <span class="number">5</span>] = <span class="string">"Fri"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Sat"</span>] = <span class="number">6</span>] = <span class="string">"Sat"</span>;</span><br><span class="line">&#125;)(<span class="built_in">Days</span> || (<span class="built_in">Days</span> = &#123;&#125;));</span><br></pre></td></tr></table></figure>
<h2 id="手动赋值"><a href="#手动赋值" class="headerlink" title="手动赋值"></a>手动赋值</h2><p>我们也可以给枚举项手动赋值：</p>
<figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">enum Days &#123;Sun = <span class="number">7</span>, Mon = <span class="number">1</span>, Tue, Wed, Thu, Fri, Sat&#125;;</span><br><span class="line"></span><br><span class="line">console.log(Days[<span class="string">"Sun"</span>] === <span class="number">7</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Mon"</span>] === <span class="number">1</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Tue"</span>] === <span class="number">2</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Sat"</span>] === <span class="number">6</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>上面的例子中，未手动赋值的枚举项会接着上一个枚举项递增。</p>
<p>如果未手动赋值的枚举项与手动赋值的重复了，TypeScript 是不会察觉到这一点的：</p>
<figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">enum Days &#123;Sun = <span class="number">3</span>, Mon = <span class="number">1</span>, Tue, Wed, Thu, Fri, Sat&#125;;</span><br><span class="line"></span><br><span class="line">console.log(Days[<span class="string">"Sun"</span>] === <span class="number">3</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Wed"</span>] === <span class="number">3</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="number">3</span>] === <span class="string">"Sun"</span>); <span class="comment">// false</span></span><br><span class="line">console.log(Days[<span class="number">3</span>] === <span class="string">"Wed"</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<p>上面的例子中，递增到 3 的时候与前面的 Sun 的取值重复了，但是 TypeScript 并没有报错，导致 Days[3] 的值先是 “Sun”，而后又被 “Wed” 覆盖了。编译的结果是：</p>
<figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">var</span> <span class="built_in">Days</span>;</span><br><span class="line">(function (<span class="built_in">Days</span>) &#123;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Sun"</span>] = <span class="number">3</span>] = <span class="string">"Sun"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Mon"</span>] = <span class="number">1</span>] = <span class="string">"Mon"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Tue"</span>] = <span class="number">2</span>] = <span class="string">"Tue"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Wed"</span>] = <span class="number">3</span>] = <span class="string">"Wed"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Thu"</span>] = <span class="number">4</span>] = <span class="string">"Thu"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Fri"</span>] = <span class="number">5</span>] = <span class="string">"Fri"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Sat"</span>] = <span class="number">6</span>] = <span class="string">"Sat"</span>;</span><br><span class="line">&#125;)(<span class="built_in">Days</span> || (<span class="built_in">Days</span> = &#123;&#125;));</span><br></pre></td></tr></table></figure>
<p>所以使用的时候需要注意，最好不要出现这种覆盖的情况。</p>
<p>手动赋值的枚举项可以不是数字，此时需要使用类型断言来让tsc无视类型检查 (编译出的js仍然是可用的)：</p>
<figure class="highlight capnproto"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Days</span> </span>&#123;Sun = <span class="number">7</span>, Mon, Tue, Wed, Thu, Fri, Sat = &lt;any&gt;<span class="string">"S"</span>&#125;;</span><br></pre></td></tr></table></figure>
<figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">var</span> <span class="built_in">Days</span>;</span><br><span class="line">(function (<span class="built_in">Days</span>) &#123;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Sun"</span>] = <span class="number">7</span>] = <span class="string">"Sun"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Mon"</span>] = <span class="number">8</span>] = <span class="string">"Mon"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Tue"</span>] = <span class="number">9</span>] = <span class="string">"Tue"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Wed"</span>] = <span class="number">10</span>] = <span class="string">"Wed"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Thu"</span>] = <span class="number">11</span>] = <span class="string">"Thu"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Fri"</span>] = <span class="number">12</span>] = <span class="string">"Fri"</span>;</span><br><span class="line">    <span class="built_in">Days</span>[<span class="built_in">Days</span>[<span class="string">"Sat"</span>] = <span class="string">"S"</span>] = <span class="string">"Sat"</span>;</span><br><span class="line">&#125;)(<span class="built_in">Days</span> || (<span class="built_in">Days</span> = &#123;&#125;));</span><br></pre></td></tr></table></figure>
<p>当然，手动赋值的枚举项也可以为小数或负数，此时后续未手动赋值的项的递增步长仍为 1：</p>
<figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">enum Days &#123;Sun = <span class="number">7</span>, Mon = <span class="number">1.5</span>, Tue, Wed, Thu, Fri, Sat&#125;;</span><br><span class="line"></span><br><span class="line">console.log(Days[<span class="string">"Sun"</span>] === <span class="number">7</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Mon"</span>] === <span class="number">1.5</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Tue"</span>] === <span class="number">2.5</span>); <span class="comment">// true</span></span><br><span class="line">console.log(Days[<span class="string">"Sat"</span>] === <span class="number">6.5</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>
<h2 id="常数项和计算所得项"><a href="#常数项和计算所得项" class="headerlink" title="常数项和计算所得项"></a>常数项和计算所得项</h2><p>枚举项有两种类型：常数项（constant member）和计算所得项（computed member）。</p>
<p>前面我们所举的例子都是常数项，一个典型的计算所得项的例子：</p>
<figure class="highlight capnproto"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Color</span> </span>&#123;Red, Green, Blue = <span class="string">"blue"</span>.length&#125;;</span><br></pre></td></tr></table></figure>
<p>上面的例子中，”blue”.length 就是一个计算所得项。</p>
<p>上面的例子不会报错，但是如果紧接在计算所得项后面的是未手动赋值的项，那么它就会因为无法获得初始值而报错：</p>
<figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">enum Color &#123;Red = <span class="string">"red"</span>.<span class="built_in">length</span>, Green, Blue&#125;;</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">1</span>,<span class="number">33</span>): <span class="built_in">error</span> TS1061: Enum <span class="built_in">member</span> must have initializer.</span><br><span class="line">// index.ts(<span class="number">1</span>,<span class="number">40</span>): <span class="built_in">error</span> TS1061: Enum <span class="built_in">member</span> must have initializer.</span><br></pre></td></tr></table></figure>
<p>下面是常数项和计算所得项的完整定义，部分引用自中文手册 - 枚举：</p>
<p>当满足以下条件时，枚举成员被当作是常数：</p>
<ul>
<li>不具有初始化函数并且之前的枚举成员是常数。在这种情况下，当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法，那么它的初始值为 0。</li>
<li>枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集，它可以在编译阶段求值。当一个表达式满足下面条件之一时，它就是一个常数枚举表达式：<ul>
<li>数字字面量</li>
<li>引用之前定义的常数枚举成员（可以是在不同的枚举类型中定义的）如果这个成员是在同一个枚举类型中定义的，可以使用非限定名来引用</li>
<li>带括号的常数枚举表达式</li>
<li>+, -, ~ 一元运算符应用于常数枚举表达式</li>
<li>+, -, *, /, %, &lt;&lt;, &gt;&gt;, &gt;&gt;&gt;, &amp;, |, ^ 二元运算符，常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为NaN或Infinity，则会在编译阶段报错</li>
</ul>
</li>
</ul>
<p>所有其它情况的枚举成员被当作是需要计算得出的值。</p>
<h2 id="常数枚举"><a href="#常数枚举" class="headerlink" title="常数枚举"></a>常数枚举</h2><p>常数枚举是使用 <strong>const enum</strong> 定义的枚举类型：</p>
<figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">const <span class="class"><span class="keyword">enum</span> <span class="title">Directions</span> &#123;</span></span><br><span class="line">    Up,</span><br><span class="line">    Down,</span><br><span class="line">    Left,</span><br><span class="line">    Right</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];</span><br></pre></td></tr></table></figure>
<p>常数枚举与普通枚举的区别是，它会在编译阶段被删除，并且不能包含计算成员。</p>
<p>上例的编译结果是：</p>
<p><code>var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];</code></p>
<p>假如包含了计算成员，则会在编译阶段报错：</p>
<figure class="highlight capnproto"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">enum</span> <span class="title">Color</span> </span>&#123;Red, Green, Blue = <span class="string">"blue"</span>.length&#125;;</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">1</span>,<span class="number">38</span>): error TS2474: In '<span class="keyword">const</span>' <span class="class"><span class="keyword">enum</span> <span class="title">declarations</span> member initializer must be constant expression.</span></span><br></pre></td></tr></table></figure>
<h2 id="外部枚举"><a href="#外部枚举" class="headerlink" title="外部枚举"></a>外部枚举</h2><p>外部枚举（Ambient Enums）是使用 <strong>declare enum</strong> 定义的枚举类型：</p>
<figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">declare <span class="class"><span class="keyword">enum</span> <span class="title">Directions</span> &#123;</span></span><br><span class="line">    Up,</span><br><span class="line">    Down,</span><br><span class="line">    Left,</span><br><span class="line">    Right</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];</span><br></pre></td></tr></table></figure>
<p>之前提到过，<strong>declare</strong> 定义的类型只会用于编译时的检查，编译结果中会被删除。</p>
<p>上例的编译结果是：<br><figure class="highlight mathematica"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var directions = [Directions.<span class="keyword">Up</span>, Directions.<span class="keyword">Down</span>, Directions.<span class="keyword">Left</span>, Directions.<span class="keyword">Right</span>];</span><br></pre></td></tr></table></figure></p>
<p>外部枚举与声明语句一样，常出现在声明文件中。</p>
<p>同时使用 <strong>declare</strong> 和 <strong>const</strong> 也是可以的：<br><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">declare const <span class="class"><span class="keyword">enum</span> <span class="title">Directions</span> &#123;</span></span><br><span class="line">    Up,</span><br><span class="line">    Down,</span><br><span class="line">    Left,</span><br><span class="line">    Right</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];</span><br></pre></td></tr></table></figure></p>
<p>编译结果：<br><figure class="highlight lsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var directions = [<span class="number">0</span> <span class="comment">/* Up */</span>, <span class="number">1</span> <span class="comment">/* Down */</span>, <span class="number">2</span> <span class="comment">/* Left */</span>, <span class="number">3</span> <span class="comment">/* Right */</span>];</span><br></pre></td></tr></table></figure></p>
<p>TypeScript 的枚举类型的概念来源于 C#。</p>
<h1 id="类"><a href="#类" class="headerlink" title="类"></a>类</h1><p>传统方法中，JavaScript 通过构造函数实现类的概念，通过原型链实现继承。而在 ES6 中，我们终于迎来了 class。</p>
<p>TypeScript 除了实现了所有 ES6 中的类的功能以外，还添加了一些新的用法。</p>
<p>这一节主要介绍类的用法，下一节再介绍如何定义类的类型。</p>
<h2 id="类的概念"><a href="#类的概念" class="headerlink" title="类的概念"></a>类的概念</h2><p>虽然 JavaScript 中有类的概念，但是可能大多数 JavaScript 程序员并不是非常熟悉类，这里对类相关的概念做一个简单的介绍。</p>
<ul>
<li>类(Class)：定义了一件事物的抽象特点，包含它的属性和方法</li>
<li>对象（Object）：类的实例，通过 new 生成</li>
<li>面向对象（OOP）的三大特性：封装、继承、多态</li>
<li>封装（Encapsulation）：将对数据的操作细节隐藏起来，只暴露对外的接口。外界调用端不需要（也不可能）知道细节，就能通过对外提供的接口来访问该对象，同时也保证了外界无法任意更改对象内部的数据</li>
<li>继承（Inheritance）：子类继承父类，子类除了拥有父类的所有特性外，还有一些更具体的特性</li>
<li>多态（Polymorphism）：由继承而产生了相关的不同的类，对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal，但是分别实现了自己的 eat 方法。此时针对某一个实例，我们无需了解它是 Cat 还是 Dog，就可以直接调用 eat 方法，程序会自动判断出来应该如何执行 eat</li>
<li>存取器（getter &amp; setter）：用以改变属性的读取和赋值行为</li>
<li>修饰符（Modifiers）：修饰符是一些关键字，用于限定成员或类型的性质。比如 public 表示公有属性或方法</li>
<li>抽象类（Abstract Class）：抽象类是供其他类继承的基类，抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现</li>
<li>接口（Interfaces）：不同类之间公有的属性或方法，可以抽象成一个接口。接口可以被类实现（implements）。一个类只能继承自另一个类，但是可以实现多个接口</li>
</ul>
<h2 id="ES6-中类的用法"><a href="#ES6-中类的用法" class="headerlink" title="ES6 中类的用法"></a>ES6 中类的用法</h2><p>下面我们先回顾一下 ES6 中类的用法，更详细的介绍可以参考 ECMAScript 6 入门 - Class。</p>
<h3 id="属性和方法"><a href="#属性和方法" class="headerlink" title="属性和方法"></a>属性和方法</h3><p>使用 class 定义类，使用 constructor 定义构造函数。</p>
<p>通过 new 生成新实例的时候，会自动调用构造函数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span>(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    sayHi() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">`My name is <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span>`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> Animal(<span class="string">'Jack'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(a.sayHi()); <span class="comment">// My name is Jack</span></span><br></pre></td></tr></table></figure>
<h3 id="类的继承"><a href="#类的继承" class="headerlink" title="类的继承"></a>类的继承</h3><p>使用 extends 关键字实现继承，子类中使用 super 关键字来调用父类的构造函数和方法。</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Cat</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    constructor(name) &#123;</span><br><span class="line">        <span class="keyword">super</span>(name); <span class="comment">// 调用父类的 constructor(name)</span></span><br><span class="line">        console.log(<span class="keyword">this</span>.name);</span><br><span class="line">    &#125;</span><br><span class="line">    sayHi() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="symbol">'Meow</span>, ' + <span class="keyword">super</span>.sayHi(); <span class="comment">// 调用父类的 sayHi()</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let c = <span class="keyword">new</span> <span class="type">Cat</span>(<span class="symbol">'To</span>m'); <span class="comment">// Tom</span></span><br><span class="line">console.log(c.sayHi()); <span class="comment">// Meow, My name is Tom</span></span><br></pre></td></tr></table></figure>
<h3 id="存取器"><a href="#存取器" class="headerlink" title="存取器"></a>存取器</h3><p>使用 <strong>getter</strong> 和 <strong>setter</strong> 可以改变属性的赋值和读取行为：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span>(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">get</span> name() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">'Jack'</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">set</span> name(value) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'setter: '</span> + value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> Animal(<span class="string">'Kitty'</span>); <span class="comment">// setter: Kitty</span></span><br><span class="line">a.name = <span class="string">'Tom'</span>; <span class="comment">// setter: Tom</span></span><br><span class="line"><span class="built_in">console</span>.log(a.name); <span class="comment">// Jack</span></span><br></pre></td></tr></table></figure>
<h3 id="静态方法"><a href="#静态方法" class="headerlink" title="静态方法"></a>静态方法</h3><p>使用 <strong>static</strong> 修饰符修饰的方法称为静态方法，它们不需要实例化，而是直接通过类来调用：</p>
<figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title">Animal</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="title">isAnimal</span>(<span class="params">a</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> a instanceof Animal;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> Animal(<span class="string">'Jack'</span>);</span><br><span class="line">Animal.isAnimal(a); <span class="comment">// true</span></span><br><span class="line">a.isAnimal(a); <span class="comment">// TypeError: a.isAnimal is not a function</span></span><br></pre></td></tr></table></figure>
<h2 id="ES7-中类的用法"><a href="#ES7-中类的用法" class="headerlink" title="ES7 中类的用法"></a>ES7 中类的用法</h2><p>ES7 中有一些关于类的提案，TypeScript 也实现了它们，这里做一个简单的介绍。</p>
<h3 id="实例属性"><a href="#实例属性" class="headerlink" title="实例属性"></a>实例属性</h3><p>ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义，ES7 提案中可以直接在类里面定义：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    name = <span class="string">'Jack'</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>() &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> Animal();</span><br><span class="line"><span class="built_in">console</span>.log(a.name); <span class="comment">// Jack</span></span><br></pre></td></tr></table></figure>
<h3 id="静态属性"><a href="#静态属性" class="headerlink" title="静态属性"></a>静态属性</h3><p>ES7 提案中，可以使用 <strong>static</strong> 定义一个静态属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> num = <span class="number">42</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>() &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(Animal.num); <span class="comment">// 42</span></span><br></pre></td></tr></table></figure>
<h2 id="TypeScript-中类的用法"><a href="#TypeScript-中类的用法" class="headerlink" title="TypeScript 中类的用法"></a>TypeScript 中类的用法</h2><h3 id="public-private-和-protected"><a href="#public-private-和-protected" class="headerlink" title="public private 和 protected"></a>public private 和 protected</h3><p>TypeScript 可以使用三种访问修饰符（Access Modifiers），分别是 public、private 和 protected。</p>
<ul>
<li>public 修饰的属性或方法是公有的，可以在任何地方被访问到，默认所有的属性和方法都是 public 的</li>
<li>private 修饰的属性或方法是私有的，不能在声明它的类的外部访问</li>
<li>protected 修饰的属性或方法是受保护的，它和 private 类似，区别是它在子类中也是允许被访问的</li>
</ul>
<p>下面举一些例子：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    <span class="keyword">public</span> name;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">constructor</span>(<span class="params">name</span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = <span class="keyword">new</span> Animal(<span class="string">'Jack'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(a.name); <span class="comment">// Jack</span></span><br><span class="line">a.name = <span class="string">'Tom'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(a.name); <span class="comment">// Tom</span></span><br></pre></td></tr></table></figure>
<p>上面的例子中，name 被设置为了 public，所以直接访问实例的 name 属性是允许的。</p>
<p>很多时候，我们希望有的属性是无法直接存取的，这时候就可以用 private 了：</p>
<figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">class Animal &#123;</span><br><span class="line">    private<span class="built_in"> name</span>;</span><br><span class="line">    public constructor<span class="built_in">(name</span>) &#123;</span><br><span class="line">        this<span class="built_in">.name</span> =<span class="built_in"> name</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = new Animal(<span class="string">'Jack'</span>);</span><br><span class="line">console.log(a<span class="built_in">.name</span>); // Jack</span><br><span class="line">a<span class="built_in">.name</span> = <span class="string">'Tom'</span>;</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">9</span>,<span class="number">13</span>):<span class="built_in"> error</span> TS2341: Property <span class="string">'name'</span> <span class="literal">is</span> private <span class="keyword">and</span> <span class="keyword">only</span> accessible within class <span class="string">'Animal'</span>.</span><br><span class="line">// index.ts(<span class="number">10</span>,<span class="number">1</span>):<span class="built_in"> error</span> TS2341: Property <span class="string">'name'</span> <span class="literal">is</span> private <span class="keyword">and</span> <span class="keyword">only</span> accessible within class <span class="string">'Animal'</span>.</span><br></pre></td></tr></table></figure>
<p>需要注意的是，TypeScript 编译之后的代码中，并没有限制 private 属性在外部的可访问性。</p>
<p>上面的例子编译后的代码是：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Animal = (<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">Animal</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Animal;</span><br><span class="line">&#125;());</span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> Animal(<span class="string">'Jack'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(a.name);</span><br><span class="line">a.name = <span class="string">'Tom'</span>;</span><br></pre></td></tr></table></figure>
<p>使用 private 修饰的属性或方法，在子类中也是不允许访问的：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> name;</span><br><span class="line">    public constructor(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Cat</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    constructor(name) &#123;</span><br><span class="line">        <span class="keyword">super</span>(name);</span><br><span class="line">        console.log(<span class="keyword">this</span>.name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.</span></span><br></pre></td></tr></table></figure>
<p>而如果是用 protected 修饰，则允许在子类中访问：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    <span class="keyword">protected</span> name;</span><br><span class="line">    public constructor(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Cat</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    constructor(name) &#123;</span><br><span class="line">        <span class="keyword">super</span>(name);</span><br><span class="line">        console.log(<span class="keyword">this</span>.name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="抽象类"><a href="#抽象类" class="headerlink" title="抽象类"></a>抽象类</h3><p><strong>abstract</strong> 用于定义抽象类和其中的抽象方法。</p>
<p>什么是抽象类？</p>
<p>首先，抽象类是不允许被实例化的：</p>
<figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">abstract class Animal &#123;</span><br><span class="line">   <span class="keyword"> public</span> name;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> constructor</span>(name) &#123;</span><br><span class="line">        this.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> abstract</span> sayHi();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let a =<span class="built_in"> new </span>Animal('Jack');</span><br><span class="line"></span><br><span class="line">// index.ts(9,11): error TS2511: Cannot create an<span class="built_in"> instance </span>of the<span class="keyword"> abstract</span> class 'Animal'.</span><br></pre></td></tr></table></figure>
<p>上面的例子中，我们定义了一个抽象类 Animal，并且定义了一个抽象方法 sayHi。在实例化抽象类的时候报错了。</p>
<p>其次，抽象类中的抽象方法必须被子类实现：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    public name;</span><br><span class="line">    public constructor(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    public <span class="keyword">abstract</span> sayHi();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Cat</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    public eat() &#123;</span><br><span class="line">        console.log(`$&#123;<span class="keyword">this</span>.name&#125; is eating.`);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let cat = <span class="keyword">new</span> <span class="type">Cat</span>(<span class="symbol">'To</span>m');</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.</span></span><br></pre></td></tr></table></figure>
<p>上面的例子中，我们定义了一个类 Cat 继承了抽象类 Animal，但是没有实现抽象方法 sayHi，所以编译报错了。</p>
<p>下面是一个正确使用抽象类的例子：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    public name;</span><br><span class="line">    public constructor(name) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    public <span class="keyword">abstract</span> sayHi();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Cat</span> <span class="keyword">extends</span> <span class="title">Animal</span> </span>&#123;</span><br><span class="line">    public sayHi() &#123;</span><br><span class="line">        console.log(`<span class="type">Meow</span>, <span class="type">My</span> name is $&#123;<span class="keyword">this</span>.name&#125;`);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let cat = <span class="keyword">new</span> <span class="type">Cat</span>(<span class="symbol">'To</span>m');</span><br></pre></td></tr></table></figure>
<p>上面的例子中，我们实现了抽象方法 sayHi，编译通过了。</p>
<p>需要注意的是，即使是抽象方法，TypeScript 的编译结果中，仍然会存在这个类，上面的代码的编译结果是：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> __extends = (<span class="keyword">this</span> &amp;&amp; <span class="keyword">this</span>.__extends) || <span class="function"><span class="keyword">function</span> (<span class="params">d, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> p <span class="keyword">in</span> b) <span class="keyword">if</span> (b.hasOwnProperty(p)) d[p] = b[p];</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__</span>(<span class="params"></span>) </span>&#123; <span class="keyword">this</span>.constructor = d; &#125;</span><br><span class="line">    d.prototype = b === <span class="literal">null</span> ? <span class="built_in">Object</span>.create(b) : (__.prototype = b.prototype, <span class="keyword">new</span> __());</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> Animal = (<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">Animal</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Animal;</span><br><span class="line">&#125;());</span><br><span class="line"><span class="keyword">var</span> Cat = (<span class="function"><span class="keyword">function</span> (<span class="params">_super</span>) </span>&#123;</span><br><span class="line">    __extends(Cat, _super);</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">Cat</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        _super.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    Cat.prototype.sayHi = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'Meow, My name is '</span> + <span class="keyword">this</span>.name);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> Cat;</span><br><span class="line">&#125;(Animal));</span><br><span class="line"><span class="keyword">var</span> cat = <span class="keyword">new</span> Cat(<span class="string">'Tom'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="类的类型"><a href="#类的类型" class="headerlink" title="类的类型"></a>类的类型</h3><p>给类加上 TypeScript 的类型很简单，与接口类似：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    sayHi(): <span class="built_in">string</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">`My name is <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span>`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a: Animal = <span class="keyword">new</span> Animal(<span class="string">'Jack'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(a.sayHi()); <span class="comment">// My name is Jack</span></span><br></pre></td></tr></table></figure>
<h1 id="类与接口"><a href="#类与接口" class="headerlink" title="类与接口"></a>类与接口</h1><p>之前学习过，接口（Interfaces）可以用于对「对象的形状（Shape）」进行描述。</p>
<p>这一章主要介绍接口的另一个用途，对类的一部分行为进行抽象。</p>
<h2 id="类实现接口"><a href="#类实现接口" class="headerlink" title="类实现接口"></a>类实现接口</h2><p>实现（implements）是面向对象中的一个重要概念。一般来讲，一个类只能继承自另一个类，有时候不同类之间可以有一些共有的特性，这时候就可以把特性提取成接口（interfaces），用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。</p>
<p>举例来说，门是一个类，防盗门是门的子类。如果防盗门有一个报警器的功能，我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类，车，也有报警器的功能，就可以考虑把报警器提取出来，作为一个接口，防盗门和车都去实现它：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">interface <span class="type">Alarm</span> &#123;</span><br><span class="line">    alert();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Door</span> </span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SecurityDoor</span> <span class="keyword">extends</span> <span class="title">Door</span> <span class="title">implements</span> <span class="title">Alarm</span> </span>&#123;</span><br><span class="line">    alert() &#123;</span><br><span class="line">        console.log(<span class="symbol">'SecurityDoor</span> alert');</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Car</span> <span class="title">implements</span> <span class="title">Alarm</span> </span>&#123;</span><br><span class="line">    alert() &#123;</span><br><span class="line">        console.log(<span class="symbol">'Car</span> alert');</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>一个类可以实现多个接口：</p>
<figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">interface</span> <span class="selector-tag">Alarm</span> &#123;</span><br><span class="line">    <span class="selector-tag">alert</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">interface</span> <span class="selector-tag">Light</span> &#123;</span><br><span class="line">    <span class="selector-tag">lightOn</span>();</span><br><span class="line">    <span class="selector-tag">lightOff</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">class</span> <span class="selector-tag">Car</span> <span class="selector-tag">implements</span> <span class="selector-tag">Alarm</span>, <span class="selector-tag">Light</span> &#123;</span><br><span class="line">    <span class="selector-tag">alert</span>() &#123;</span><br><span class="line">        <span class="selector-tag">console</span><span class="selector-class">.log</span>(<span class="string">'Car alert'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="selector-tag">lightOn</span>() &#123;</span><br><span class="line">        <span class="selector-tag">console</span><span class="selector-class">.log</span>(<span class="string">'Car light on'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="selector-tag">lightOff</span>() &#123;</span><br><span class="line">        <span class="selector-tag">console</span><span class="selector-class">.log</span>(<span class="string">'Car light off'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上例中，Car 实现了 Alarm 和 Light 接口，既能报警，也能开关车灯。</p>
<h2 id="接口继承接口"><a href="#接口继承接口" class="headerlink" title="接口继承接口"></a>接口继承接口</h2><p>接口与接口之间可以是继承关系：</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">interface Alarm &#123;</span><br><span class="line">    alert();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">interface LightableAlarm extends Alarm &#123;</span><br><span class="line">    lightOn();</span><br><span class="line">    lightOff();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上例中，我们使用 extends 使 LightableAlarm 继承 Alarm。</p>
<h2 id="接口继承类"><a href="#接口继承类" class="headerlink" title="接口继承类"></a>接口继承类</h2><p>接口也可以继承类：</p>
<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> &#123;</span></span><br><span class="line"><span class="symbol">    x:</span> number;</span><br><span class="line"><span class="symbol">    y:</span> number;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Point3d</span> <span class="keyword">extends</span> <span class="title">Point</span> &#123;</span></span><br><span class="line"><span class="symbol">    z:</span> number;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let <span class="string">point3d:</span> Point3d = &#123;<span class="string">x:</span> <span class="number">1</span>, <span class="string">y:</span> <span class="number">2</span>, <span class="string">z:</span> <span class="number">3</span>&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="混合类型"><a href="#混合类型" class="headerlink" title="混合类型"></a>混合类型</h3><p>之前学习过，可以使用接口的方式来定义一个函数需要符合的形状：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SearchFunc &#123;</span><br><span class="line">    (source: <span class="built_in">string</span>, subString: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> mySearch: SearchFunc;</span><br><span class="line">mySearch = <span class="function"><span class="keyword">function</span>(<span class="params">source: <span class="built_in">string</span>, subString: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> source.search(subString) !== <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>有时候，一个函数还可以有自己的属性和方法：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Counter &#123;</span><br><span class="line">    (start: <span class="built_in">number</span>): <span class="built_in">string</span>;</span><br><span class="line">    interval: <span class="built_in">number</span>;</span><br><span class="line">    reset(): <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getCounter</span>(<span class="params"></span>): <span class="title">Counter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> counter = &lt;Counter&gt;<span class="function"><span class="keyword">function</span> (<span class="params">start: <span class="built_in">number</span></span>) </span>&#123; &#125;;</span><br><span class="line">    counter.interval = <span class="number">123</span>;</span><br><span class="line">    counter.reset = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123; &#125;;</span><br><span class="line">    <span class="keyword">return</span> counter;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = getCounter();</span><br><span class="line">c(<span class="number">10</span>);</span><br><span class="line">c.reset();</span><br><span class="line">c.interval = <span class="number">5.0</span>;</span><br></pre></td></tr></table></figure>
<h1 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h1><p>泛型（Generics）是指在定义函数、接口或类的时候，不预先指定具体的类型，而在使用的时候再指定类型的一种特性。</p>
<h2 id="简单的例子-4"><a href="#简单的例子-4" class="headerlink" title="简单的例子"></a>简单的例子</h2><p>首先，我们来实现一个函数 createArray，它可以创建一个指定长度的数组，同时将每一项都填充一个默认值：</p>
<figure class="highlight julia"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> createArray(length: number, value: any): <span class="built_in">Array</span>&lt;any&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> result = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">        result[i] = value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createArray(<span class="number">3</span>, <span class="string">'x'</span>); // [<span class="string">'x'</span>, <span class="string">'x'</span>, <span class="string">'x'</span>]</span><br></pre></td></tr></table></figure>
<p>上例中，我们使用了之前提到过的数组泛型来定义返回值的类型。</p>
<p>这段代码编译不会报错，但是一个显而易见的缺陷是，它并没有准确的定义返回值的类型：</p>
<p><code>Array&lt;any&gt;</code> 允许数组的每一项都为任意类型。但是我们预期的是，数组中每一项都应该是输入的 value 的类型。</p>
<p>这时候，泛型就派上用场了：</p>
<figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> createArray&lt;<span class="literal">T</span>&gt;(length: number, value: <span class="literal">T</span>): Array&lt;<span class="literal">T</span>&gt; &#123;</span><br><span class="line">    let result: <span class="literal">T</span>[] = [];</span><br><span class="line">    <span class="keyword">for</span> (let i = <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">        result[i] = value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createArray&lt;string&gt;(<span class="number">3</span>, <span class="string">'x'</span>); // [<span class="string">'x'</span>, <span class="string">'x'</span>, <span class="string">'x'</span>]</span><br></pre></td></tr></table></figure>
<p>上例中，我们在函数名后添加了 <t>，其中 T 用来指代任意输入的类型，在后面的输入 value: T 和输出 Array<t> 中即可使用了。</t></t></p>
<p>接着在调用的时候，可以指定它具体的类型为 string。当然，也可以不手动指定，而让类型推论自动推算出来：</p>
<figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> createArray&lt;<span class="literal">T</span>&gt;(length: number, value: <span class="literal">T</span>): Array&lt;<span class="literal">T</span>&gt; &#123;</span><br><span class="line">    let result: <span class="literal">T</span>[] = [];</span><br><span class="line">    <span class="keyword">for</span> (let i = <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">        result[i] = value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createArray(<span class="number">3</span>, <span class="string">'x'</span>); // [<span class="string">'x'</span>, <span class="string">'x'</span>, <span class="string">'x'</span>]</span><br></pre></td></tr></table></figure>
<h2 id="多个类型参数"><a href="#多个类型参数" class="headerlink" title="多个类型参数"></a>多个类型参数</h2><p>定义泛型的时候，可以一次定义多个类型参数：</p>
<figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">function swap&lt;<span class="symbol">T</span>, <span class="symbol">U</span>&gt;(tuple: [<span class="symbol">T</span>, <span class="symbol">U</span>]): [<span class="symbol">U</span>, <span class="symbol">T</span>] &#123;</span><br><span class="line">    return [tuple[<span class="number">1</span>], tuple[<span class="number">0</span>]];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">swap([<span class="number">7</span>, <span class="string">'seven'</span>]); // [<span class="string">'seven'</span>, <span class="number">7</span>]</span><br></pre></td></tr></table></figure>
<p>上例中，我们定义了一个 swap 函数，用来交换输入的元组。</p>
<h2 id="泛型约束"><a href="#泛型约束" class="headerlink" title="泛型约束"></a>泛型约束</h2><p>在函数内部使用泛型变量的时候，由于事先不知道它是哪种类型，所以不能随意的操作它的属性或方法：</p>
<figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span>&gt;<span class="params">(arg: T)</span></span>: T &#123;</span><br><span class="line">    console.<span class="built_in">log</span>(<span class="built_in">arg</span>.length);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">arg</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">2</span>,<span class="number">19</span>): <span class="built_in">error</span> TS2339: Property <span class="string">'length'</span> does <span class="keyword">not</span> exist on <span class="built_in">type</span> <span class="string">'T'</span>.</span><br></pre></td></tr></table></figure>
<p>上例中，泛型 T 不一定包含属性 length，所以编译的时候报错了。</p>
<p>这时，我们可以对泛型进行约束，只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束：</p>
<figure class="highlight fortran"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Lengthwise &#123;</span><br><span class="line">    length: <span class="keyword">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span></span> loggingIdentity&lt;T <span class="keyword">extends</span> Lengthwise&gt;(arg: T): T &#123;</span><br><span class="line">    console.<span class="built_in">log</span>(arg.length);</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上例中，我们使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状，也就是必须包含 length 属性。</p>
<p>此时如果调用 loggingIdentity 的时候，传入的 arg 不包含 length，那么在编译阶段就会报错了：</p>
<figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">interface Lengthwise &#123;</span><br><span class="line">    length: number;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span> <span class="title">extends</span> <span class="title">Lengthwise</span>&gt;<span class="params">(arg: T)</span></span>: T &#123;</span><br><span class="line">    console.<span class="built_in">log</span>(<span class="built_in">arg</span>.length);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">arg</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">loggingIdentity(<span class="number">7</span>);</span><br><span class="line"></span><br><span class="line">// index.ts(<span class="number">10</span>,<span class="number">17</span>): <span class="built_in">error</span> TS2345: Argument of <span class="built_in">type</span> <span class="string">'7'</span> is <span class="keyword">not</span> assignable to parameter of <span class="built_in">type</span> <span class="string">'Lengthwise'</span>.</span><br></pre></td></tr></table></figure>
<p>多个类型参数之间也可以互相约束：</p>
<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">function copyFields&lt;T <span class="keyword">extends</span> U, U&gt;(<span class="string">target:</span> T, <span class="string">source:</span> U): T &#123;</span><br><span class="line">    <span class="keyword">for</span> (let id <span class="keyword">in</span> source) &#123;</span><br><span class="line">        target[id] = (&lt;T&gt;source)[id];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> target;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let x = &#123; <span class="string">a:</span> <span class="number">1</span>, <span class="string">b:</span> <span class="number">2</span>, <span class="string">c:</span> <span class="number">3</span>, <span class="string">d:</span> <span class="number">4</span> &#125;;</span><br><span class="line"></span><br><span class="line">copyFields(x, &#123; <span class="string">b:</span> <span class="number">10</span>, <span class="string">d:</span> <span class="number">20</span> &#125;);</span><br></pre></td></tr></table></figure>
<p>上例中，我们使用了两个类型参数，其中要求 T 继承 U，这样就保证了 U 上不会出现 T 中不存在的字段。</p>
<h2 id="泛型接口"><a href="#泛型接口" class="headerlink" title="泛型接口"></a>泛型接口</h2><p>之前学习过，可以使用接口的方式来定义一个函数需要符合的形状：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SearchFunc &#123;</span><br><span class="line">  (source: <span class="built_in">string</span>, subString: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> mySearch: SearchFunc;</span><br><span class="line">mySearch = <span class="function"><span class="keyword">function</span>(<span class="params">source: <span class="built_in">string</span>, subString: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> source.search(subString) !== <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>当然也可以使用含有泛型的接口来定义函数的形状：</p>
<figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">interface CreateArrayFunc &#123;</span><br><span class="line">    &lt;<span class="literal">T</span>&gt;(length: number, value: <span class="literal">T</span>): Array&lt;<span class="literal">T</span>&gt;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">let createArray: CreateArrayFunc;</span><br><span class="line">createArray = <span class="keyword">function</span>&lt;<span class="literal">T</span>&gt;(length: number, value: <span class="literal">T</span>): Array&lt;<span class="literal">T</span>&gt; &#123;</span><br><span class="line">    let result: <span class="literal">T</span>[] = [];</span><br><span class="line">    <span class="keyword">for</span> (let i = <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">        result[i] = value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createArray(<span class="number">3</span>, <span class="string">'x'</span>); // [<span class="string">'x'</span>, <span class="string">'x'</span>, <span class="string">'x'</span>]</span><br></pre></td></tr></table></figure>
<p>进一步，我们可以把泛型参数提前到接口名上：<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> CreateArrayFunc&lt;T&gt; &#123;</span><br><span class="line">    (length: <span class="built_in">number</span>, value: T): <span class="built_in">Array</span>&lt;T&gt;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> createArray: CreateArrayFunc&lt;<span class="built_in">any</span>&gt;;</span><br><span class="line">createArray = <span class="function"><span class="keyword">function</span>&lt;<span class="title">T</span>&gt;(<span class="params">length: <span class="built_in">number</span>, value: T</span>): <span class="title">Array</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> result: T[] = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; length; i++) &#123;</span><br><span class="line">        result[i] = value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createArray(<span class="number">3</span>, <span class="string">'x'</span>); <span class="comment">// ['x', 'x', 'x']</span></span><br></pre></td></tr></table></figure></p>
<p>注意，此时在使用泛型接口的时候，需要定义泛型的类型。</p>
<h2 id="泛型类"><a href="#泛型类" class="headerlink" title="泛型类"></a>泛型类</h2><p>与泛型接口类似，泛型也可以用于类的类型定义中：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GenericNumber</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    zeroValue: T;</span><br><span class="line">    add: <span class="function">(<span class="params">x: T, y: T</span>) =&gt;</span> T;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myGenericNumber = <span class="keyword">new</span> GenericNumber&lt;number&gt;();</span><br><span class="line">myGenericNumber.zeroValue = <span class="number">0</span>;</span><br><span class="line">myGenericNumber.add = <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>) </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>
<h2 id="泛型参数的默认类型"><a href="#泛型参数的默认类型" class="headerlink" title="泛型参数的默认类型"></a>泛型参数的默认类型</h2><p>在 TypeScript 2.3 以后，我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数，从实际值参数中也无法推测出时，这个默认类型就会起作用。</p>
<figure class="highlight matlab"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createArray</span>&lt;<span class="title">T</span> = <span class="title">string</span>&gt;<span class="params">(length: number, value: T)</span>: <span class="title">Array</span>&lt;<span class="title">T</span>&gt; &#123;</span></span><br><span class="line">    let result: T[] = [];</span><br><span class="line">    <span class="keyword">for</span> (let <span class="built_in">i</span> = <span class="number">0</span>; <span class="built_in">i</span> &lt; <span class="built_in">length</span>; <span class="built_in">i</span>++) &#123;</span><br><span class="line">        result[<span class="built_in">i</span>] = value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="声明合并"><a href="#声明合并" class="headerlink" title="声明合并"></a>声明合并</h1><p>如果定义了两个相同名字的函数、接口或类，那么它们会合并成一个类型：</p>
<h2 id="函数的合并"><a href="#函数的合并" class="headerlink" title="函数的合并"></a>函数的合并</h2><p>之前学习过，我们可以使用重载定义多个函数类型：</p>
<figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span><span class="built_in"> reverse</span>(x:<span class="built_in"> number</span>):<span class="built_in"> number</span>;</span><br><span class="line"><span class="keyword">function</span><span class="built_in"> reverse</span>(x:<span class="built_in"> string</span>):<span class="built_in"> string</span>;</span><br><span class="line"><span class="keyword">function</span><span class="built_in"> reverse</span>(x:<span class="built_in"> number</span> |<span class="built_in"> string</span>):<span class="built_in"> number</span> |<span class="built_in"> string</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (typeof x === <span class="string">'number'</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Number(x.toString().split(<span class="string">''</span>)<span class="built_in">.reverse</span>().join(<span class="string">''</span>));</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (typeof x === <span class="string">'string'</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> x.split(<span class="string">''</span>)<span class="built_in">.reverse</span>().join(<span class="string">''</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="接口的合并"><a href="#接口的合并" class="headerlink" title="接口的合并"></a>接口的合并</h2><p>接口中的属性在合并时会简单的合并到一个接口中：</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">interface Alarm &#123;</span><br><span class="line">    price: number;</span><br><span class="line">&#125;</span><br><span class="line">interface Alarm &#123;</span><br><span class="line">    weight: number;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">interface</span> <span class="selector-tag">Alarm</span> &#123;</span><br><span class="line">    <span class="attribute">price</span>: number;</span><br><span class="line">    <span class="attribute">weight</span>: number;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>注意，合并的属性的类型必须是唯一的：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    price: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    price: <span class="built_in">number</span>;  <span class="comment">// 虽然重复了，但是类型都是 `number`，所以不会报错</span></span><br><span class="line">    weight: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    price: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    price: <span class="built_in">string</span>;  <span class="comment">// 类型不一致，会报错</span></span><br><span class="line">    weight: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type.  Variable 'price' must be of type 'number', but here has type 'string'.</span></span><br></pre></td></tr></table></figure>
<p>接口中方法的合并，与函数的合并一样：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    price: <span class="built_in">number</span>;</span><br><span class="line">    alert(s: <span class="built_in">string</span>): <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    weight: <span class="built_in">number</span>;</span><br><span class="line">    alert(s: <span class="built_in">string</span>, n: <span class="built_in">number</span>): <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Alarm &#123;</span><br><span class="line">    price: <span class="built_in">number</span>;</span><br><span class="line">    weight: <span class="built_in">number</span>;</span><br><span class="line">    alert(s: <span class="built_in">string</span>): <span class="built_in">string</span>;</span><br><span class="line">    alert(s: <span class="built_in">string</span>, n: <span class="built_in">number</span>): <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="类的合并"><a href="#类的合并" class="headerlink" title="类的合并"></a>类的合并</h2><p>类的合并与接口的合并规则一致。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/27/JavaScript-模块化模式/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/27/JavaScript-模块化模式/" itemprop="url">JavaScript 模块化模式</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-27T07:11:07+08:00">
                2018-09-27
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/javascript/" itemprop="url" rel="index">
                    <span itemprop="name">javascript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="模块化模式"><a href="#模块化模式" class="headerlink" title="模块化模式"></a>模块化模式</h1><h1 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h1><p>模块是任何健壮的应用程序体系结构不可或缺的一部分，特点是有助于保持应用项目的代码单元既能清晰地分离又有组织。</p>
<p>在JavaScript中，实现模块有几个选项，他们包括：</p>
<ul>
<li>模块化模式</li>
<li>对象表示法</li>
<li>AMD模块</li>
<li>CommonJS 模块</li>
<li>ECMAScript Harmony 模块</li>
</ul>
<p>我们在书中后面的现代模块化JavaScript设计模式章节中将探讨这些选项中的最后三个。</p>
<p>模块化模式是基于对象的文字部分，所以首先对于更新我们对它们的知识是很有意义的。</p>
<h1 id="对象字面值"><a href="#对象字面值" class="headerlink" title="对象字面值"></a>对象字面值</h1><p>在对象字面值的标记里，一个对象被描述为一组以逗号分隔的名称/值对括在大括号（{}）的集合。对象内部的名称可以是字符串或是标记符后跟着一个冒号”:”。在对象里最后一个名称/值对不应该以”,”为结束符，因为这样会导致错误。<br><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myObjectLiteral = &#123;</span><br><span class="line"></span><br><span class="line">    variableKey: variableValue,</span><br><span class="line"></span><br><span class="line">    functionKey: <span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">    &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p>
<p>对象字面值不要求使用新的操作实例，但是不能够在结构体开始使用，因为打开”{“可能被解释为一个块的开始。在对象外新的成员会被加载，使用分配如下：smyModule.property = “someValue”; 下面我们可以看到一个更完整的使用对象字面值定义一个模块的例子：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myModule = &#123;</span><br><span class="line"></span><br><span class="line">  myProperty: <span class="string">"someValue"</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 对象字面值包含了属性和方法（properties and methods）.</span></span><br><span class="line">  <span class="comment">// 例如，我们可以定义一个模块配置进对象：</span></span><br><span class="line">  myConfig: &#123;</span><br><span class="line">    useCaching: <span class="literal">true</span>,</span><br><span class="line">    language: <span class="string">"en"</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 非常基本的方法</span></span><br><span class="line">  myMethod: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">"Where in the world is Paul Irish today?"</span> );</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 输出基于当前配置（&lt;span&gt;configuration&lt;/span&gt;）的一个值</span></span><br><span class="line">  myMethod2: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">"Caching is:"</span> + ( <span class="keyword">this</span>.myConfig.useCaching ) ? <span class="string">"enabled"</span> : <span class="string">"disabled"</span> );</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 重写当前的配置（configuration）</span></span><br><span class="line">  myMethod3: <span class="function"><span class="keyword">function</span>(<span class="params"> newConfig </span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ( <span class="keyword">typeof</span> newConfig === <span class="string">"object"</span> ) &#123;</span><br><span class="line">      <span class="keyword">this</span>.myConfig = newConfig;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="keyword">this</span>.myConfig.language);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: Where in the world is Paul Irish today?</span></span><br><span class="line">myModule.myMethod();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: enabled</span></span><br><span class="line">myModule.myMethod2();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出: fr</span></span><br><span class="line">myModule.myMethod3(&#123;</span><br><span class="line">  language: <span class="string">"fr"</span>,</span><br><span class="line">  useCaching: <span class="literal">false</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p>
<p>使用对象字面值可以协助封装和组织你的代码。如果你想近一步了解对象字面值可以阅读 Rebecca Murphey 写过的关于此类话题的更深入的文章(depth)。</p>
<p>也就是说，如果我们选择了这种技术，我们可能对模块模式有同样的兴趣。即使使用对象字面值，但也只有一个函数的返回值。</p>
<h1 id="模块化模式-1"><a href="#模块化模式-1" class="headerlink" title="模块化模式"></a>模块化模式</h1><p>模块化模式最初被定义为一种对传统软件工程中的类提供私有和公共封装的方法。</p>
<p>在JavaScript中，模块化模式用来进一步模拟类的概念，通过这样一种方式：我们可以在一个单一的对象中包含公共/私有的方法和变量，从而从全局范围中屏蔽特定的部分。这个结果是可以减少我们的函数名称与在页面中其他脚本区域定义的函数名称冲突的可能性。</p>
<h2 id="私有信息"><a href="#私有信息" class="headerlink" title="私有信息"></a>私有信息</h2><p>模块模式使用闭包的方式来将”私有信息”,状态和组织结构封装起来。提供了一种将公有和私有方法，变量封装混合在一起的方式，这种方式防止内部信息泄露到全局中，从而避免了和其它开发者接口发生冲图的可能性。在这种模式下只有公有的API 会返回，其它将全部保留在闭包的私有空间中。</p>
<p>这种方法提供了一个比较清晰的解决方案，在只暴露一个接口供其它部分使用的情况下，将执行繁重任务的逻辑保护起来。这个模式非常类似于立即调用函数式表达式(IIFE-查看命名空间相关章节获取更多信息)，但是这种模式返回的是对象，而立即调用函数表达式返回的是一个函数。</p>
<p>需要注意的是，在javascript事实上没有一个显式的真正意义上的”私有性”概念，因为与传统语言不同，javascript没有访问修饰符。从技术上讲，变量不能被声明为公有的或者私有的，因此我们使用函数域的方式去模拟这个概念。在模块模式中，因为闭包的缘故，声明的变量或者方法只在模块内部有效。在返回对象中定义的变量或者方法可以供任何人使用。</p>
<h2 id="历史"><a href="#历史" class="headerlink" title="历史"></a>历史</h2><p>从历史角度来看，模块模式最初是在2003年由一群人共同发展出来的，这其中包括Richard Cornford。后来通过Douglas Crockford的演讲，逐渐变得流行起来。另外一件事情是，如果你曾经用过雅虎的YUI库，你会看到其中的一些特性和模块模式非常类似，而这种情况的原因是在创建YUI框架的时候，模块模式极大的影响了YUI的设计。</p>
<h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><p>下面这个例子通过创建一个自包含的模块实现了模块模式。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> testModule = (<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> counter = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line"></span><br><span class="line">    incrementCounter: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> counter++;</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    resetCounter: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log( <span class="string">"counter value prior to reset: "</span> + counter );</span><br><span class="line">      counter = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Usage:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Increment our counter</span></span><br><span class="line">testModule.incrementCounter();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Check the counter value and reset</span></span><br><span class="line"><span class="comment">// Outputs: 1</span></span><br><span class="line">testModule.resetCounter();</span><br></pre></td></tr></table></figure></p>
<p>在这里我们看到，其它部分的代码不能直接访问我们的incrementCounter() 或者 resetCounter()的值。counter变量被完全从全局域中隔离起来了，因此其表现的就像一个私有变量一样，它的存在只局限于模块的闭包内部，因此只有两个函数可以访问counter。我们的方法是有名字空间限制的，因此在我们代码的测试部分，我们需要给所有函数调用前面加上模块的名字(例如”testModule”)。</p>
<p>当使用模块模式时，我们会发现通过使用简单的模板，对于开始使用模块模式非常有用。下面是一个模板包含了命名空间，公共变量和私有变量。<br><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myNamespace = (<span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> myPrivateVar, myPrivateMethod;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// A private counter variable</span></span><br><span class="line">  myPrivateVar = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// A private function which logs any arguments</span></span><br><span class="line">  myPrivateMethod = <span class="function"><span class="keyword">function</span><span class="params">( foo )</span> </span>&#123;</span><br><span class="line">      console.log( foo );</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// A public variable</span></span><br><span class="line">    myPublicVar: <span class="string">"foo"</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// A public function utilizing privates</span></span><br><span class="line">    myPublicFunction: <span class="function"><span class="keyword">function</span><span class="params">( bar )</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// Increment our private counter</span></span><br><span class="line">      myPrivateVar++;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// Call our private method using bar</span></span><br><span class="line">      myPrivateMethod( bar );</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure></p>
<p>看一下另外一个例子，下面我们看到一个使用这种模式实现的购物车。这个模块完全自包含在一个叫做basketModule 全局变量中。模块中的购物车数组是私有的，应用的其它部分不能直接读取。只存在与模块的闭包中，因此只有可以访问其域的方法可以访问这个变量。<br><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> basketModule = (<span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// privates</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> basket = [];</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">doSomethingPrivate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">doSomethingElsePrivate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Return an object exposed to the public</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Add items to our basket</span></span><br><span class="line">    addItem: <span class="function"><span class="keyword">function</span><span class="params">( values )</span> </span>&#123;</span><br><span class="line">      basket.push(values);</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Get the count of items in the basket</span></span><br><span class="line">    getItemCount: <span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> basket.length;</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Public alias to a  private function</span></span><br><span class="line">    doSomething: doSomethingPrivate,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Get the total value of items in the basket</span></span><br><span class="line">    getTotal: <span class="function"><span class="keyword">function</span> <span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">var</span> q = <span class="keyword">this</span>.getItemCount(),</span><br><span class="line">          p = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (q--) &#123;</span><br><span class="line">        p += basket[q].price;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> p;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;());</span><br></pre></td></tr></table></figure></p>
<p>在模块内部，你可能注意到我们返回了应外一个对象。这个自动赋值给了basketModule 因此我们可以这样和这个对象交互。<br><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// basketModule returns an object with a public API we can use</span></span><br><span class="line"></span><br><span class="line"><span class="selector-tag">basketModule</span><span class="selector-class">.addItem</span>(&#123;</span><br><span class="line">  <span class="attribute">item</span>: <span class="string">"bread"</span>,</span><br><span class="line">  <span class="attribute">price</span>: <span class="number">0.5</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">basketModule</span><span class="selector-class">.addItem</span>(&#123;</span><br><span class="line">  <span class="attribute">item</span>: <span class="string">"butter"</span>,</span><br><span class="line">  <span class="attribute">price</span>: <span class="number">0.3</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Outputs: 2</span></span><br><span class="line"><span class="selector-tag">console</span><span class="selector-class">.log</span>( basketModule.getItemCount() );</span><br><span class="line"></span><br><span class="line"><span class="comment">// Outputs: 0.8</span></span><br><span class="line"><span class="selector-tag">console</span><span class="selector-class">.log</span>( basketModule.getTotal() );</span><br><span class="line"></span><br><span class="line"><span class="comment">// However, the following will not work:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Outputs: undefined</span></span><br><span class="line"><span class="comment">// This is because the basket itself is not exposed as a part of our</span></span><br><span class="line"><span class="comment">// the public API</span></span><br><span class="line"><span class="selector-tag">console</span><span class="selector-class">.log</span>( basketModule.basket );</span><br><span class="line"></span><br><span class="line"><span class="comment">// This also won't work as it only exists within the scope of our</span></span><br><span class="line"><span class="comment">// basketModule closure, but not the returned public object</span></span><br><span class="line"><span class="selector-tag">console</span><span class="selector-class">.log</span>( basket );</span><br></pre></td></tr></table></figure></p>
<p>上面的方法都处于basketModule 的名字空间中。</p>
<p>请注意在上面的basket模块中 域函数是如何在我们所有的函数中被封装起来的，以及我们如何立即调用这个域函数，并且将返回值保存下来。这种方式有以下的优势：</p>
<ul>
<li>可以创建只能被我们模块访问的私有函数。这些函数没有暴露出来（只有一些API是暴露出来的），它们被认为是完全私有的。</li>
<li>当我们在一个调试器中，需要发现哪个函数抛出异常的时候，可以很容易的看到调用栈，因为这些函数是正常声明的并且是命名的函数。</li>
<li>正如过去 T.J Crowder 指出的，这种模式同样可以让我们在不同的情况下返回不同的函数。我见过有开发者使用这种技巧用于执行UA（尿检，抽样检查）测试，目的是为了在他们的模块里面针对IE专门提供一条代码路径，但是现在我们也可以简单的使用特征检测达到相同的目的。</li>
</ul>
<h1 id="模块模式的变体"><a href="#模块模式的变体" class="headerlink" title="模块模式的变体"></a>模块模式的变体</h1><h2 id="Import-mixins-导入混合"><a href="#Import-mixins-导入混合" class="headerlink" title="Import mixins(导入混合)"></a>Import mixins(导入混合)</h2><p>这个变体展示了如何将全局（例如 jQuery, Underscore）作为一个参数传入模块的匿名函数。这种方式允许我们导入全局，并且按照我们的想法在本地为这些全局起一个别名。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Global module</span></span><br><span class="line"><span class="keyword">var</span> myModule = (<span class="function"><span class="keyword">function</span> (<span class="params"> jQ, _ </span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">privateMethod1</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        jQ(<span class="string">".container"</span>).html(<span class="string">"test"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">privateMethod2</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log( _.min([<span class="number">10</span>, <span class="number">5</span>, <span class="number">100</span>, <span class="number">2</span>, <span class="number">1000</span>]) );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span>&#123;</span><br><span class="line">        publicMethod: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">            privateMethod1();               </span><br><span class="line">        &#125;           </span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Pull in jQuery and Underscore</span></span><br><span class="line">&#125;( jQuery, _ ));</span><br><span class="line"></span><br><span class="line">myModule.publicMethod();</span><br></pre></td></tr></table></figure></p>
<h2 id="Exports（导出）"><a href="#Exports（导出）" class="headerlink" title="Exports（导出）"></a>Exports（导出）</h2><p>这个变体允许我们声明全局对象而不用使用它们，同样也支持在下一个例子中我们将会看到的全局导入的概念。<br><figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Global module</span></span><br><span class="line">var myModule = (<span class="function"><span class="keyword">function</span> <span class="params">()</span> &#123;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Module object</span></span><br><span class="line">  var <span class="function"><span class="keyword">module</span> =</span> &#123;&#125;,</span><br><span class="line">    privateVariable = <span class="string">"Hello World"</span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">privateMethod</span><span class="params">()</span> &#123;</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">module</span>.<span class="title">publicProperty</span> =</span> <span class="string">"Foobar"</span>;</span><br><span class="line">  <span class="function"><span class="keyword">module</span>.<span class="title">publicMethod</span> =</span> <span class="function"><span class="keyword">function</span> <span class="params">()</span> &#123;</span></span><br><span class="line">    console.<span class="built_in">log</span>( privateVariable );</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  return <span class="function"><span class="keyword">module</span>;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">&#125;<span class="params">()</span>);</span></span><br></pre></td></tr></table></figure></p>
<p>工具箱和框架特定的模块模式实现。</p>
<h2 id="Dojo"><a href="#Dojo" class="headerlink" title="Dojo"></a>Dojo</h2><p>Dojo提供了一个方便的方法 dojo.setObject() 来设置对象。这需要将以”.”符号为第一个参数的分隔符，如：myObj.parent.child 是指定义在”myOjb”内部的一个对象“parent”，它的一个属性为”child”。使用setObject()方法允许我们设置children 的值，可以创建路径传递过程中的任何对象即使这些它们根本不存在。</p>
<p>例如，如果我们声明商店命名空间的对象basket.coreas，可以实现使用传统的方式如下：<br><figure class="highlight llvm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">var <span class="keyword">store</span> = window.<span class="keyword">store</span> || &#123;&#125;<span class="comment">;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">if ( !store["basket"] ) &#123;</span></span><br><span class="line"><span class="comment">  store.basket = &#123;&#125;;</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">if ( !store.basket["core"] ) &#123;</span></span><br><span class="line"><span class="comment">  store.basket.core = &#123;&#125;;</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">store.basket.core = &#123;</span></span><br><span class="line"><span class="comment">  // ...rest of our logic</span></span><br><span class="line"><span class="comment">&#125;;</span></span><br></pre></td></tr></table></figure></p>
<p>或使用Dojo1.7（AMD兼容的版本）及以上如下：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>([<span class="string">"dojo/_base/customStore"</span>], <span class="function"><span class="keyword">function</span>(<span class="params"> store </span>)</span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// using dojo.setObject()</span></span><br><span class="line">  store.setObject( <span class="string">"basket.core"</span>, (<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">var</span> basket = [];</span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">function</span> <span class="title">privateMethod</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">          <span class="built_in">console</span>.log(basket);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">          publicMethod: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">                  privateMethod();</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">  &#125;()));</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p>
<p>欲了解更多关于dojo.setObject（）方法的信息，请参阅官方文档 documentation</p>
<h2 id="ExtJS"><a href="#ExtJS" class="headerlink" title="ExtJS"></a>ExtJS</h2><p>对于这些使用Sencha的ExtJS的人们，你们很幸运，因为官方文档包含一些例子，用于展示如何正确地在框架里面使用模块模式。</p>
<p>下面我们可以看到一个例子关于如何定义一个名字空间，然后填入一个包含有私有和公有API的模块。除了一些语义上的不同之外，这个例子和使用vanilla javascript 实现的模块模式非常相似。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// create namespace</span></span><br><span class="line">Ext.namespace(<span class="string">"myNameSpace"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// create application</span></span><br><span class="line">myNameSpace.app = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// do NOT access DOM from here; elements don't exist yet</span></span><br><span class="line">  <span class="comment">// private variables</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> btn1,</span><br><span class="line">      privVar1 = <span class="number">11</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// private functions</span></span><br><span class="line">  <span class="keyword">var</span> btn1Handler = <span class="function"><span class="keyword">function</span> (<span class="params"> button, event </span>) </span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log( <span class="string">"privVar1="</span> + privVar1 );</span><br><span class="line">      <span class="built_in">console</span>.log( <span class="string">"this.btn1Text="</span> + <span class="keyword">this</span>.btn1Text );</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// public space</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="comment">// public properties, e.g. strings to translate</span></span><br><span class="line">    btn1Text: <span class="string">"Button 1"</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// public methods</span></span><br><span class="line">    init: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> ( Ext.Ext2 ) &#123;</span><br><span class="line"></span><br><span class="line">        btn1 = <span class="keyword">new</span> Ext.Button(&#123;</span><br><span class="line">          renderTo: <span class="string">"btn1-ct"</span>,</span><br><span class="line">          text: <span class="keyword">this</span>.btn1Text,</span><br><span class="line">          handler: btn1Handler</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">        btn1 = <span class="keyword">new</span> Ext.Button( <span class="string">"btn1-ct"</span>, &#123;</span><br><span class="line">          text: <span class="keyword">this</span>.btn1Text,</span><br><span class="line">          handler: btn1Handler</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;();</span><br></pre></td></tr></table></figure></p>
<h2 id="YUI"><a href="#YUI" class="headerlink" title="YUI"></a>YUI</h2><p>类似地，我们也可以使用YUI3来实现模块模式。下面的例子很大程度上是基于原始由Eric Miraglia实现的YUI本身的模块模式，但是和vanillla Javascript 实现的版本比较起来差异不是很大。<br><figure class="highlight monkey"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">Y.namespace( <span class="string">"store.basket"</span> ) = (<span class="function"><span class="keyword">function</span> (</span>) &#123;</span><br><span class="line"></span><br><span class="line">    var myPrivateVar, myPrivateMethod;</span><br><span class="line"></span><br><span class="line">    // <span class="keyword">private</span> variables:</span><br><span class="line">    myPrivateVar = <span class="string">"I can be accessed only within Y.store.basket."</span>;</span><br><span class="line"></span><br><span class="line">    // <span class="keyword">private</span> <span class="function"><span class="keyword">method</span>:</span></span><br><span class="line">    myPrivateMethod = <span class="function"><span class="keyword">function</span> (</span>) &#123;</span><br><span class="line">        Y.<span class="built_in">log</span>( <span class="string">"I can be accessed only from within YAHOO.store.basket"</span> );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        myPublicProperty: <span class="string">"I'm a public property."</span>,</span><br><span class="line"></span><br><span class="line">        myPublicMethod: <span class="function"><span class="keyword">function</span> (</span>) &#123;</span><br><span class="line">            Y.<span class="built_in">log</span>( <span class="string">"I'm a public method."</span> );</span><br><span class="line"></span><br><span class="line">            // Within basket, I can access <span class="string">"private"</span> vars <span class="literal">and</span> methods:</span><br><span class="line">            Y.<span class="built_in">log</span>( myPrivateVar );</span><br><span class="line">            Y.<span class="built_in">log</span>( myPrivateMethod() );</span><br><span class="line"></span><br><span class="line">            // The native scope of myPublicMethod is store so we can</span><br><span class="line">            // access <span class="keyword">public</span> members using <span class="string">"this"</span>:</span><br><span class="line">            Y.<span class="built_in">log</span>( this.myPublicProperty );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure></p>
<h2 id="jQuery"><a href="#jQuery" class="headerlink" title="jQuery"></a>jQuery</h2><p>因为jQuery编码规范没有规定插件如何实现模块模式，因此有很多种方式可以实现模块模式。Ben Cherry 之间提供一种方案，因为模块之间可能存在大量的共性，因此通过使用函数包装器封装模块的定义。</p>
<p>在下面的例子中，定义了一个library 函数，这个函数声明了一个新的库，并且在新的库（例如 模块）创建的时候，自动将初始化函数绑定到document的ready上。<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">library</span>(<span class="params"> module </span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  $( <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> ( <span class="built_in">module</span>.init ) &#123;</span><br><span class="line">      <span class="built_in">module</span>.init();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">module</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> myLibrary = library(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    init: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="comment">// module implementation</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;());</span><br></pre></td></tr></table></figure></p>
<h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><p>既然我们已经看到单例模式很有用，为什么还是使用模块模式呢？首先，对于有面向对象背景的开发者来讲，至少从javascript语言上来讲，模块模式相对于真正的封装概念更清晰。</p>
<p>其次，模块模式支持私有数据-因此，在模块模式中，公共部分代码可以访问私有数据，但是在模块外部，不能访问类的私有部分（没开玩笑！感谢David Engfer 的玩笑）。<br>缺点</p>
<p>模块模式的缺点是因为我们采用不同的方式访问公有和私有成员，因此当我们想要改变这些成员的可见性的时候，我们不得不在所有使用这些成员的地方修改代码。</p>
<p>我们也不能在对象之后添加的方法里面访问这些私有变量。也就是说，很多情况下，模块模式很有用，并且当使用正确的时候，潜在地可以改善我们代码的结构。</p>
<p>其它缺点包括不能为私有成员创建自动化的单元测试，以及在紧急修复bug时所带来的额外的复杂性。根本没有可能可以对私有成员打补丁。相反地，我们必须覆盖所有的使用存在bug私有成员的公共方法。开发者不能简单的扩展私有成员，因此我们需要记得，私有成员并非它们表面上看上去那么具有扩展性。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/26/JavaScript-构造器模式/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/26/JavaScript-构造器模式/" itemprop="url">JavaScript 构造器模式</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-26T22:44:31+08:00">
                2018-09-26
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/javascript/" itemprop="url" rel="index">
                    <span itemprop="name">javascript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="构造器模式"><a href="#构造器模式" class="headerlink" title="构造器模式"></a>构造器模式</h1><p>在面向对象编程中，构造器是一个当新建对象的内存被分配后，用来初始化该对象的一个特殊函数。在JavaScript中几乎所有的东西都是对象，我们经常会对对象的构造器十分感兴趣。</p>
<p>对象构造器是被用来创建特殊类型的对象的，首先它要准备使用的对象，其次在对象初次被创建时，通过接收参数，构造器要用来对成员的属性和方法进行赋值。</p>
<h1 id="对象创建"><a href="#对象创建" class="headerlink" title="对象创建"></a>对象创建</h1><p>下面是我们创建对象的三种基本方式:</p>
<p>下面的每一种都会创建一个新的对象:</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="keyword">new</span><span class="type">Object</span> = &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// or</span></span><br><span class="line"><span class="keyword">var</span> <span class="keyword">new</span><span class="type">Object</span> = Object.create( <span class="literal">null</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">// or</span></span><br><span class="line"><span class="keyword">var</span> <span class="keyword">new</span><span class="type">Object</span> = <span class="keyword">new</span> <span class="type">Object</span>();</span><br></pre></td></tr></table></figure>
<p>最后一个例子中”Object”构造器创建了一个针对特殊值的对象包装，只不过这里没有传值给它，所以它将会返回一个空对象。</p>
<p>有四种方式可以将一个键值对赋给一个对象:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ECMAScript 3 兼容形式</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1\. “点号”法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置属性</span></span><br><span class="line">newObject.someKey = <span class="string">"Hello World"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取属性</span></span><br><span class="line"><span class="keyword">var</span> key = newObject.someKey;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2\. “方括号”法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置属性</span></span><br><span class="line">newObject[<span class="string">"someKey"</span>] = <span class="string">"Hello World"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取属性</span></span><br><span class="line"><span class="keyword">var</span> key = newObject[<span class="string">"someKey"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// ECMAScript 5 仅兼容性形式</span></span><br><span class="line"><span class="comment">// For more information see: http://kangax.github.com/es5-compat-table/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3\. Object.defineProperty方式</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置属性</span></span><br><span class="line"><span class="built_in">Object</span>.defineProperty( newObject, <span class="string">"someKey"</span>, &#123;</span><br><span class="line">    value: <span class="string">"for more control of the property's behavior"</span>,</span><br><span class="line">    writable: <span class="literal">true</span>,</span><br><span class="line">    enumerable: <span class="literal">true</span>,</span><br><span class="line">    configurable: <span class="literal">true</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果上面的方式你感到难以阅读，可以简短的写成下面这样：</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> defineProp = <span class="function"><span class="keyword">function</span> (<span class="params"> obj, key, value </span>)</span>&#123;</span><br><span class="line">  config.value = value;</span><br><span class="line">  <span class="built_in">Object</span>.defineProperty( obj, key, config );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为了使用它，我们要创建一个“person”对象</span></span><br><span class="line"><span class="keyword">var</span> person = <span class="built_in">Object</span>.create( <span class="literal">null</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用属性构造对象</span></span><br><span class="line">defineProp( person, <span class="string">"car"</span>,  <span class="string">"Delorean"</span> );</span><br><span class="line">defineProp( person, <span class="string">"dateOfBirth"</span>, <span class="string">"1981"</span> );</span><br><span class="line">defineProp( person, <span class="string">"hasBeard"</span>, <span class="literal">false</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4\. Object.defineProperties方式</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置属性</span></span><br><span class="line"><span class="built_in">Object</span>.defineProperties( newObject, &#123;</span><br><span class="line"></span><br><span class="line">  <span class="string">"someKey"</span>: &#123; </span><br><span class="line">    value: <span class="string">"Hello World"</span>, </span><br><span class="line">    writable: <span class="literal">true</span> </span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="string">"anotherKey"</span>: &#123; </span><br><span class="line">    value: <span class="string">"Foo bar"</span>, </span><br><span class="line">    writable: <span class="literal">false</span> </span><br><span class="line">  &#125; </span><br><span class="line"></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3和4中的读取属行可用1和2中的任意一种</span></span><br></pre></td></tr></table></figure>
<p>在这本书的后面一点，这些方法会被用于继承，如下：<br><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建一个继承与Person的赛车司机</span></span><br><span class="line">var <span class="built_in">driver</span> = Object.create( person );</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置司机的属性</span></span><br><span class="line">defineProp(<span class="built_in">driver</span>, <span class="string">"topSpeed"</span>, <span class="string">"100mph"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取继承的属性 (1981)</span></span><br><span class="line">console.<span class="built_in">log</span>( <span class="built_in">driver</span>.dateOfBirth );</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取我们设置的属性 (100mph)</span></span><br><span class="line">console.<span class="built_in">log</span>( <span class="built_in">driver</span>.topSpeed );</span><br></pre></td></tr></table></figure></p>
<h1 id="基础构造器"><a href="#基础构造器" class="headerlink" title="基础构造器"></a>基础构造器</h1><p>正如我们先前所看到的，在ES6没有出来之前Javascript不支持类的概念，但它有一种与对象一起工作的构造器函数。使用new关键字来调用该函数，我们可以告诉Javascript把这个函数当做一个构造器来用,它可以用自己所定义的成员来初始化一个对象。</p>
<p>在这个构造器内部，关键字this引用到刚被创建的对象。回到对象创建，一个基本的构造函数看起来像这样:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Car</span>(<span class="params"> model, year, miles </span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">this</span>.model = model;</span><br><span class="line">  <span class="keyword">this</span>.year = year;</span><br><span class="line">  <span class="keyword">this</span>.miles = miles;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">this</span>.toString = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.model + <span class="string">" has done "</span> + <span class="keyword">this</span>.miles + <span class="string">" miles"</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 我们可以示例化一个Car</span></span><br><span class="line"><span class="keyword">var</span> civic = <span class="keyword">new</span> Car( <span class="string">"Honda Civic"</span>, <span class="number">2009</span>, <span class="number">20000</span> );</span><br><span class="line"><span class="keyword">var</span> mondeo = <span class="keyword">new</span> Car( <span class="string">"Ford Mondeo"</span>, <span class="number">2010</span>, <span class="number">5000</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">// 打开浏览器控制台查看这些对象toString()方法的输出值</span></span><br><span class="line"><span class="comment">// output of the toString() method being called on</span></span><br><span class="line"><span class="comment">// these objects</span></span><br><span class="line"><span class="built_in">console</span>.log( civic.toString() );</span><br><span class="line"><span class="built_in">console</span>.log( mondeo.toString() );</span><br></pre></td></tr></table></figure>
<p>上面这是个简单版本的构造器模式，但它还是有些问题。一个是难以继承，另一个是每个Car构造函数创建的对象中，toString()之类的函数都被重新定义。这不是非常好，理想的情况是所有Car类型的对象都应该引用同一个函数。 这要谢谢 ECMAScript3和ECMAScript5-兼容版，对于构造对象他们提供了另外一些选择，解决限制小菜一碟。</p>
<h1 id="使用“原型”的构造器"><a href="#使用“原型”的构造器" class="headerlink" title="使用“原型”的构造器"></a>使用“原型”的构造器</h1><p>在Javascript中函数有一个prototype的属性。当我们调用Javascript的构造器创建一个对象时，构造函数prototype上的属性对于所创建的对象来说都看见。照这样，就可以创建多个访问相同prototype的Car对象了。下面，我们来扩展一下原来的例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Car</span>(<span class="params"> model, year, miles </span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">this</span>.model = model;</span><br><span class="line">  <span class="keyword">this</span>.year = year;</span><br><span class="line">  <span class="keyword">this</span>.miles = miles;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注意这里我们使用Note here that we are using Object.prototype.newMethod 而不是</span></span><br><span class="line"><span class="comment">// Object.prototype ，以避免我们重新定义原型对象</span></span><br><span class="line">Car.prototype.toString = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>.model + <span class="string">" has done "</span> + <span class="keyword">this</span>.miles + <span class="string">" miles"</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用:</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> civic = <span class="keyword">new</span> Car( <span class="string">"Honda Civic"</span>, <span class="number">2009</span>, <span class="number">20000</span> );</span><br><span class="line"><span class="keyword">var</span> mondeo = <span class="keyword">new</span> Car( <span class="string">"Ford Mondeo"</span>, <span class="number">2010</span>, <span class="number">5000</span> );</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log( civic.toString() );</span><br><span class="line"><span class="built_in">console</span>.log( mondeo.toString() );</span><br></pre></td></tr></table></figure>
<p>通过上面代码，单个toString()实例被所有的Car对象所共享了。</p>
<h1 id="ES6的构造器模式"><a href="#ES6的构造器模式" class="headerlink" title="ES6的构造器模式"></a>ES6的构造器模式</h1><p>建议使用ES6的用法来实现构造器模式，ES6定义Class可以继承。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//ES6 </span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Car</span></span>&#123;</span><br><span class="line">     <span class="keyword">constructor</span>(model,year,miles)&#123;</span><br><span class="line">         <span class="keyword">this</span>.model = model;</span><br><span class="line">         <span class="keyword">this</span>.year = year;</span><br><span class="line">         <span class="keyword">this</span>.miles = miles;</span><br><span class="line">     &#125;</span><br><span class="line">    </span><br><span class="line">     tostring ()&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.model + <span class="string">" has done "</span> + <span class="keyword">this</span>.miles + <span class="string">" miles"</span>;</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">var</span> civic = new Car( <span class="string">"Honda Civic"</span>, <span class="number">2009</span>, <span class="number">20000</span> );</span><br><span class="line">   <span class="keyword">var</span> mondeo = new Car( <span class="string">"Ford Mondeo"</span>, <span class="number">2010</span>, <span class="number">5000</span> );</span><br><span class="line">   </span><br><span class="line">   console.log( civic.tostring() ); <span class="comment">//Honda Civic has done 20000 miles</span></span><br><span class="line">   console.log( mondeo.tostring() ); <span class="comment">//Ford Mondeo has done 5000 miles</span></span><br></pre></td></tr></table></figure>
<p>继承实例：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Car</span></span>&#123;</span><br><span class="line">    constructor(model,year,miles)&#123;</span><br><span class="line">        <span class="keyword">this</span>.model = model;</span><br><span class="line">        <span class="keyword">this</span>.year = year;</span><br><span class="line">        <span class="keyword">this</span>.miles = miles;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">     tostring ()&#123;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>.model + <span class="string">" has done "</span> + <span class="keyword">this</span>.miles + <span class="string">" miles"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">kaCar</span> <span class="keyword">extends</span> <span class="title">Car</span></span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> civic = <span class="keyword">new</span> kaCar( <span class="string">"Honda Civic"</span>, <span class="number">2009</span>, <span class="number">20000</span> );</span><br><span class="line">  <span class="keyword">var</span> mondeo = <span class="keyword">new</span> kaCar( <span class="string">"Ford Mondeo"</span>, <span class="number">2010</span>, <span class="number">5000</span> );</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  console.log( civic.tostring() );  <span class="comment">//Honda Civic has done 20000 miles</span></span><br><span class="line">  console.log( mondeo.tostring() );  <span class="comment">//Ford Mondeo has done 5000 miles</span></span><br></pre></td></tr></table></figure>
<p>从上面实例我们可以看出kaCar完全继承了Car的tostring()方法。</p>
<h1 id="TypeScript的构造器模式"><a href="#TypeScript的构造器模式" class="headerlink" title="TypeScript的构造器模式"></a>TypeScript的构造器模式</h1><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Car&#123;</span><br><span class="line">      model:<span class="built_in">string</span>;</span><br><span class="line">      year:<span class="built_in">number</span>;</span><br><span class="line">      miles:<span class="built_in">number</span>;</span><br><span class="line">      <span class="keyword">constructor</span>(<span class="params">model:<span class="built_in">string</span>,year:<span class="built_in">number</span>,miles:<span class="built_in">number</span></span>)&#123;</span><br><span class="line">          <span class="keyword">this</span>.model = model;</span><br><span class="line">          <span class="keyword">this</span>.year = year;</span><br><span class="line">          <span class="keyword">this</span>.miles = miles;</span><br><span class="line">      &#125;</span><br><span class="line">      tostring():<span class="built_in">string</span>&#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;<span class="keyword">this</span>.model&#125;</span> has done <span class="subst">$&#123;<span class="keyword">this</span>.miles&#125;</span> miles`</span>;</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> civic = <span class="keyword">new</span> kaCar( <span class="string">"Honda Civic"</span>, <span class="number">2009</span>, <span class="number">20000</span> );</span><br><span class="line"> <span class="keyword">var</span> mondeo = <span class="keyword">new</span> kaCar( <span class="string">"Ford Mondeo"</span>, <span class="number">2010</span>, <span class="number">5000</span> );</span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log( civic.tostring() );  <span class="comment">//Honda Civic has done 20000 miles</span></span><br><span class="line">  <span class="built_in">console</span>.log( mondeo.tostring() ); <span class="comment">//Ford Mondeo has done 5000 miles</span></span><br></pre></td></tr></table></figure>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/25/Vue-with-Typescript 系列(二) --基础篇/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/25/Vue-with-Typescript 系列(二) --基础篇/" itemprop="url">Vue-with-Typescript 系列(二) --基础篇</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-25T21:48:43+08:00">
                2018-09-25
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/typescript/" itemprop="url" rel="index">
                    <span itemprop="name">typescript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h1><p>要让 TypeScript 正确推断 Vue 组件选项中的类型，您需要使用 Vue.component 或 Vue.extend 定义组件：</p>
<p>Vue.extend的用法跟javasrcipt里面区别不大，所以这里不做过多的说明。Vue.extend用法如下：</p>
<figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">// src/components/HelloWorld.vue</span><br><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">lang</span>=<span class="string">"ts"</span>&gt;</span></span></span><br><span class="line"><span class="xml">import Vue from 'vue'</span></span><br><span class="line"><span class="xml">export default Vue.extend(</span><span class="xquery">&#123;</span></span><br><span class="line"><span class="xquery"> <span class="built_in"> name</span>: <span class="string">'HelloWorld'</span>,</span></span><br><span class="line"><span class="xquery">  data () &#123;</span></span><br><span class="line"><span class="xquery">    <span class="keyword">return</span> &#123;</span></span><br><span class="line"><span class="xquery">      msg: <span class="string">'Welcome to Your Vue.js App'</span></span></span><br><span class="line"><span class="xquery">    &#125;</span><span class="xml"></span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml">&#125;)</span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure>
<p>Vue.component的用法需要用到第三方插件库，Vue-Class-Component或Vue-Property-Decorator</p>
<h1 id="第三方插件库"><a href="#第三方插件库" class="headerlink" title="第三方插件库"></a>第三方插件库</h1><h2 id="Vue-Class-Component"><a href="#Vue-Class-Component" class="headerlink" title="Vue-Class-Component"></a>Vue-Class-Component</h2><p>vue-class-component是官方维护的TypeScript装饰器，写法比较扁平化。Vue对其做到完美兼容，如果你在声明组件时更喜欢基于类的 API，这个库一定不要错过</p>
<p>ps：用了这个装饰器之后写方法不需要额外加逗号，贼嗨~~~</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">"vue"</span>;</span><br><span class="line"><span class="keyword">import</span> Component <span class="keyword">from</span> <span class="string">"vue-class-component"</span>;</span><br><span class="line"></span><br><span class="line">@Component</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  name:string = <span class="string">'Simon Zhang'</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// computed</span></span><br><span class="line">  <span class="keyword">get</span> MyName():string &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`My name is <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span>`</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// methods</span></span><br><span class="line">  sayHello():<span class="keyword">void</span> &#123;</span><br><span class="line">    alert(<span class="string">`Hello <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span>`</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  mounted() &#123;</span><br><span class="line">    <span class="keyword">this</span>.sayHello();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这个代码如果用原生Vue语法来写的话就是这样：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">export <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="keyword">data</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      name: <span class="string">'Simon Zhang'</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  mounted () &#123;</span><br><span class="line">    <span class="keyword">this</span>.sayHello()</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  computed: &#123;</span><br><span class="line">    MyName() &#123;</span><br><span class="line">      <span class="keyword">return</span> `My name <span class="keyword">is</span> $&#123;<span class="keyword">this</span>.name&#125;`</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  methods: &#123;</span><br><span class="line">    sayHello() &#123;</span><br><span class="line">      alert(`Hello $&#123;<span class="keyword">this</span>.name&#125;`)</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="props、computed、method、data"><a href="#props、computed、method、data" class="headerlink" title="props、computed、method、data"></a>props、computed、method、data</h3><figure class="highlight django"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">v-model</span>=<span class="string">"msg"</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>prop: </span><span class="template-variable">&#123;&#123;propMessage&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>msg: </span><span class="template-variable">&#123;&#123;msg&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>helloMsg: </span><span class="template-variable">&#123;&#123;helloMsg&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>computed msg: </span><span class="template-variable">&#123;&#123;computedMsg&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">"greet"</span>&gt;</span>Greet<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="xml">import Vue from 'vue'</span></span><br><span class="line"><span class="xml">import Component from 'vue-class-component'</span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">@Component(&#123;</span></span><br><span class="line"><span class="xml">  props: &#123;</span></span><br><span class="line"><span class="xml">    propMessage: String</span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml">&#125;)</span></span><br><span class="line"><span class="xml">export default class App extends Vue &#123;</span></span><br><span class="line"><span class="xml">  // initial data</span></span><br><span class="line"><span class="xml">  msg = 123</span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  // use prop values for initial data</span></span><br><span class="line"><span class="xml">  helloMsg = 'Hello, ' + this.propMessage</span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  // lifecycle hook</span></span><br><span class="line"><span class="xml">  mounted () &#123;</span></span><br><span class="line"><span class="xml">    this.greet()</span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  // computed</span></span><br><span class="line"><span class="xml">  get computedMsg () &#123;</span></span><br><span class="line"><span class="xml">    return 'computed ' + this.msg</span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  // method</span></span><br><span class="line"><span class="xml">  greet () &#123;</span></span><br><span class="line"><span class="xml">    alert('greeting: ' + this.msg)</span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml">&#125;</span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure>
<p>相当于：<br><figure class="highlight django"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">v-model</span>=<span class="string">"msg"</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>prop: </span><span class="template-variable">&#123;&#123;propMessage&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>msg: </span><span class="template-variable">&#123;&#123;msg&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>helloMsg: </span><span class="template-variable">&#123;&#123;helloMsg&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>computed msg: </span><span class="template-variable">&#123;&#123;computedMsg&#125;&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">"greet"</span>&gt;</span>Greet<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="xml">export default &#123;</span></span><br><span class="line"><span class="xml">  name:'App',</span></span><br><span class="line"><span class="xml">  data() &#123;</span></span><br><span class="line"><span class="xml">    return &#123;</span></span><br><span class="line"><span class="xml">      msg: 123,</span></span><br><span class="line"><span class="xml">      helloMsg:'Hello, ' + this.propMessage</span></span><br><span class="line"><span class="xml">    &#125;</span></span><br><span class="line"><span class="xml">  &#125;,</span></span><br><span class="line"><span class="xml">  props: &#123;</span></span><br><span class="line"><span class="xml">    propMessage:String,</span></span><br><span class="line"><span class="xml">  &#125;,</span></span><br><span class="line"><span class="xml">  </span></span><br><span class="line"><span class="xml">  computed:&#123;</span></span><br><span class="line"><span class="xml">      computedMsg:&#123;</span></span><br><span class="line"><span class="xml">         return 'computed ' + this.msg</span></span><br><span class="line"><span class="xml">      &#125;</span></span><br><span class="line"><span class="xml">  &#125;,</span></span><br><span class="line"><span class="xml">  methods: &#123;</span></span><br><span class="line"><span class="xml">   this.greet()</span></span><br><span class="line"><span class="xml"></span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml">&#125;</span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure></p>
<h3 id="Mixins"><a href="#Mixins" class="headerlink" title="Mixins"></a>Mixins</h3><p>声明mixin的例子：<br><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// mixin.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="type">Vue</span> from <span class="symbol">'vu</span>e'</span><br><span class="line"><span class="keyword">import</span> <span class="type">Component</span> from <span class="symbol">'vue</span>-<span class="class"><span class="keyword">class</span><span class="title">-component</span>'</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">//</span> <span class="title">You</span> <span class="title">can</span> <span class="title">declare</span> <span class="title">a</span> <span class="title">mixin</span> <span class="title">as</span> <span class="title">the</span> <span class="title">same</span> <span class="title">style</span> <span class="title">as</span> <span class="title">components</span>.</span></span><br><span class="line"><span class="class"><span class="title">@Component</span></span></span><br><span class="line"><span class="class"><span class="title">export</span> <span class="title">class</span> <span class="title">MyMixin</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  mixinValue = <span class="symbol">'Hell</span>o'</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>使用mixin的例子：<br><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="type">Component</span>, &#123; mixins &#125; from <span class="symbol">'vue</span>-<span class="class"><span class="keyword">class</span><span class="title">-component</span>'</span></span><br><span class="line"><span class="class"><span class="title">import</span> <span class="title">MyMixin</span> <span class="title">from</span> '.<span class="title">/mixin</span>.<span class="title">js</span>'</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">//</span> <span class="title">Use</span> `<span class="title">mixins</span>` <span class="title">helper</span> <span class="title">function</span> <span class="title">instead</span> <span class="title">of</span> `<span class="title">Vue</span>`.</span></span><br><span class="line"><span class="class"><span class="title">//</span> `<span class="title">mixins</span>` <span class="title">can</span> <span class="title">receive</span> <span class="title">any</span> <span class="title">number</span> <span class="title">of</span> <span class="title">arguments</span>.</span></span><br><span class="line"><span class="class"><span class="title">@Component</span></span></span><br><span class="line"><span class="class"><span class="title">export</span> <span class="title">class</span> <span class="title">MyComp</span> <span class="keyword">extends</span> <span class="title">mixins</span>(<span class="params"><span class="type">MyMixin</span></span>) </span>&#123;</span><br><span class="line">  created () &#123;</span><br><span class="line">    console.log(<span class="keyword">this</span>.mixinValue) <span class="comment">// -&gt; Hello</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="Vue-Property-Decorator"><a href="#Vue-Property-Decorator" class="headerlink" title="Vue-Property-Decorator"></a>Vue-Property-Decorator</h2><p>vue-property-decorator 是在 vue-class-component 上增强了更多的结合 Vue 特性的装饰器，新增了这 7 个装饰器</p>
<ul>
<li>@Emit</li>
<li>@Provide</li>
<li>@Inject</li>
<li>@Model</li>
<li>@Prop</li>
<li>@Watch</li>
<li>@Component (从 vue-class-component 继承)</li>
</ul>
<p>下面对7个装饰器的用法做详细说明：</p>
<h3 id="Emit-event-string-装饰器"><a href="#Emit-event-string-装饰器" class="headerlink" title="@Emit(event?: string) 装饰器"></a>@Emit(event?: string) 装饰器</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="type">Vue</span>, <span class="type">Component</span>, <span class="type">Emit</span> &#125; from <span class="symbol">'vue</span>-property-decorator'</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span> <span class="comment">//每个组件必须要用@Component</span></span><br><span class="line">export <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">YourComponent</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  count = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">  <span class="meta">@Emit</span>()  <span class="comment">//this.$emit('add-to-count', n)</span></span><br><span class="line">  addToCount(n: number) &#123;</span><br><span class="line">    <span class="keyword">this</span>.count += n</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Emit</span>(<span class="symbol">'rese</span>t') <span class="comment">//this.$emit('reset')</span></span><br><span class="line">  resetCount() &#123;</span><br><span class="line">    <span class="keyword">this</span>.count = <span class="number">0</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">export <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="keyword">data</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      count: <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    addToCount(n) &#123;</span><br><span class="line">      <span class="keyword">this</span>.count += n</span><br><span class="line">      <span class="keyword">this</span>.$emit(<span class="string">'add-to-count'</span>, n)</span><br><span class="line">    &#125;,</span><br><span class="line">    resetCount() &#123;</span><br><span class="line">      <span class="keyword">this</span>.count = <span class="number">0</span></span><br><span class="line">      <span class="keyword">this</span>.$emit(<span class="string">'reset'</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>$emit的作用主要解决子组件向父组件通信问题。子组件可以使用 $emit 触发父组件的自定义事件。</p>
<blockquote>
<p>触发当前实例上的事件。附加参数都会传给监听器回调。</p>
</blockquote>
<h3 id="Provide-key-string-symbol-Inject-options-from-InjectKey-default-any-InjectKey-装饰器"><a href="#Provide-key-string-symbol-Inject-options-from-InjectKey-default-any-InjectKey-装饰器" class="headerlink" title="@Provide(key?: string | symbol) / @Inject(options?: { from?: InjectKey, default?: any } | InjectKey) 装饰器"></a>@Provide(key?: string | symbol) / @Inject(options?: { from?: InjectKey, default?: any } | InjectKey) 装饰器</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="type">Component</span>, <span class="type">Inject</span>, <span class="type">Provide</span>, <span class="type">Vue</span> &#125; from <span class="symbol">'vue</span>-property-decorator'</span><br><span class="line"></span><br><span class="line">const symbol = <span class="type">Symbol</span>(<span class="symbol">'ba</span>z')</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line">export <span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Inject</span>() foo!: string </span><br><span class="line">  <span class="meta">@Inject</span>(<span class="symbol">'ba</span>r') bar!: string</span><br><span class="line">  <span class="meta">@Inject</span>(&#123; from: <span class="symbol">'optiona</span>l', <span class="keyword">default</span>: <span class="symbol">'defaul</span>t' &#125;) optional!: string</span><br><span class="line">  <span class="meta">@Inject</span>(symbol) baz!: string</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> inject: &#123;</span></span><br><span class="line"><span class="comment">    foo: 'foo',</span></span><br><span class="line"><span class="comment">    bar: 'bar',</span></span><br><span class="line"><span class="comment">    'optional': &#123; from: 'optional', default: 'default' &#125;,</span></span><br><span class="line"><span class="comment">    [symbol]: symbol</span></span><br><span class="line"><span class="comment">  &#125;,</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line">  <span class="meta">@Provide</span>() foo = <span class="symbol">'fo</span>o'</span><br><span class="line">  <span class="meta">@Provide</span>(<span class="symbol">'ba</span>r') baz = <span class="symbol">'ba</span>r'</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment">  provide () &#123;</span></span><br><span class="line"><span class="comment">    return &#123;</span></span><br><span class="line"><span class="comment">      foo: this.foo,</span></span><br><span class="line"><span class="comment">      bar: this.baz</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line"><span class="comment">  &#125;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> symbol = Symbol(<span class="string">'baz'</span>)</span><br><span class="line"></span><br><span class="line">export <span class="keyword">const</span> MyComponent = Vue.extend(&#123;</span><br><span class="line"></span><br><span class="line">  inject: &#123;</span><br><span class="line">    foo: <span class="string">'foo'</span>,</span><br><span class="line">    bar: <span class="string">'bar'</span>,</span><br><span class="line">    <span class="string">'optional'</span>: &#123; from: <span class="string">'optional'</span>, <span class="keyword">default</span>: <span class="string">'default'</span> &#125;,</span><br><span class="line">    [symbol]: symbol</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">data</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      foo: <span class="string">'foo'</span>,</span><br><span class="line">      baz: <span class="string">'bar'</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  provide () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      foo: <span class="keyword">this</span>.foo,</span><br><span class="line">      bar: <span class="keyword">this</span>.baz</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<blockquote>
<p>provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。</p>
</blockquote>
<p>这对选项需要一起使用，以允许一个祖先组件向其所有子孙后代注入一个依赖，不论组件层次有多深，并在起上下游关系成立的时间里始终生效。如果你熟悉 React，这与 React 的上下文特性很相似。</p>
<p>provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。在该对象中你可以使用 ES2015 Symbols 作为 key，但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。</p>
<p>inject 选项应该是：</p>
<ul>
<li>一个字符串数组，或</li>
<li>一个对象，对象的 key 是本地的绑定名，value 是：<ul>
<li>在可用的注入内容中搜索用的 key (字符串或 Symbol)，或</li>
<li>一个对象，该对象的：<ul>
<li>from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)</li>
<li>default 属性是降级情况下使用的 value</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>提示：provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而，如果你传入了一个可监听的对象，那么其对象的属性还是可响应的。</p>
</blockquote>
<h3 id="Model-event-string-options-PropOptions-Constructor-Constructor-装饰器"><a href="#Model-event-string-options-PropOptions-Constructor-Constructor-装饰器" class="headerlink" title="@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {}) 装饰器"></a>@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {}) 装饰器</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="type">Vue</span>, <span class="type">Component</span>, <span class="type">Model</span> &#125; from <span class="symbol">'vue</span>-property-decorator'</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line">export <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">YourComponent</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Model</span>(<span class="symbol">'chang</span>e', &#123; <span class="class"><span class="keyword">type</span></span>: <span class="type">Boolean</span> &#125;) checked!: boolean</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">export</span> <span class="selector-tag">default</span> &#123;</span><br><span class="line">  <span class="attribute">model</span>: &#123;</span><br><span class="line">    prop: <span class="string">'checked'</span>,</span><br><span class="line">    event: <span class="string">'change'</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="selector-tag">props</span>: &#123;</span><br><span class="line">    <span class="attribute">checked</span>: &#123;</span><br><span class="line">      type: Boolean</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下，一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event，但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。</p>
<h3 id="Prop-options-PropOptions-Constructor-Constructor-装饰器"><a href="#Prop-options-PropOptions-Constructor-Constructor-装饰器" class="headerlink" title="@Prop(options: (PropOptions | Constructor[] | Constructor) = {}) 装饰器"></a>@Prop(options: (PropOptions | Constructor[] | Constructor) = {}) 装饰器</h3><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="type">Vue</span>, <span class="type">Component</span>, <span class="type">Prop</span> &#125; from <span class="symbol">'vue</span>-property-decorator'</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line">export <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">YourComponent</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Prop</span>(<span class="type">Number</span>) propA!: number</span><br><span class="line">  <span class="meta">@Prop</span>(&#123; <span class="keyword">default</span>: <span class="symbol">'default</span> value' &#125;) propB!: string</span><br><span class="line">  <span class="meta">@Prop</span>([<span class="type">String</span>, <span class="type">Boolean</span>]) propC: string | boolean</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于：<br><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">export</span> <span class="selector-tag">default</span> &#123;</span><br><span class="line">  <span class="attribute">props</span>: &#123;</span><br><span class="line">    propA: &#123;</span><br><span class="line">      type: Number</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="selector-tag">propB</span>: &#123;</span><br><span class="line">      <span class="attribute">default</span>: <span class="string">'default value'</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="selector-tag">propC</span>: &#123;</span><br><span class="line">      <span class="attribute">type</span>: [String, Boolean]</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p>props 可以是数组或对象，用于接收来自父组件的数据。props 可以是简单的数组，或者使用对象作为替代，对象允许配置高级选项，如类型检测、自定义校验和设置默认值。</p>
</blockquote>
<h1 id="总结："><a href="#总结：" class="headerlink" title="总结："></a>总结：</h1><p>Typescript基于Vue.component的实现，就需要引入第三方组件库 Vue-Property-Decorator或 Vue-Class-Component。Vue-Class-Component这个是官方维护，Vue-Property-Decorator基于Vue-Class-Component进行了增强是社区维护。</p>
<p>Typescript基于Vue.extend的实现，那么用法上不会有太大的改变。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/25/Vue-with-Typescript 系列(一) --概述/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/25/Vue-with-Typescript 系列(一) --概述/" itemprop="url">Vue-with-Typescript 系列(一) --概述</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-25T21:15:58+08:00">
                2018-09-25
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/typescript/" itemprop="url" rel="index">
                    <span itemprop="name">typescript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="推荐配置"><a href="#推荐配置" class="headerlink" title="推荐配置"></a>推荐配置</h1><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tsconfig.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="comment">// 与 Vue 的浏览器支持保持一致</span></span><br><span class="line">    <span class="string">"target"</span>: <span class="string">"es5"</span>,</span><br><span class="line">    <span class="comment">// 这可以对 `this` 上的数据属性进行更严格的推断</span></span><br><span class="line">    <span class="string">"strict"</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="comment">// 如果使用 webpack 2+ 或 rollup，可以利用 tree-shake:</span></span><br><span class="line">    <span class="string">"module"</span>: <span class="string">"es2015"</span>,</span><br><span class="line">    <span class="string">"moduleResolution"</span>: <span class="string">"node"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>注意你需要引入 strict: true (或者至少 noImplicitThis: true，这是 strict 模式的一部分) 以利用组件方法中 this 的类型检查，否则它会始终被看作 any 类型。</p>
<h1 id="开发工具链"><a href="#开发工具链" class="headerlink" title="开发工具链"></a>开发工具链</h1><h2 id="工程创建"><a href="#工程创建" class="headerlink" title="工程创建"></a>工程创建</h2><p>Vue CLI 3 可以使用 TypeScript 生成新工程。创建方式：</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 如果没有安装 Vue CLI 就先安装</span></span><br><span class="line">npm <span class="keyword">install</span> <span class="comment">--global @vue/cli</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 创建一个新工程，并选择 "Manually select features (手动选择特性)" 选项</span></span><br><span class="line">vue <span class="keyword">create</span> my-<span class="keyword">project</span>-<span class="keyword">name</span></span><br></pre></td></tr></table></figure>
<h2 id="编辑器支持"><a href="#编辑器支持" class="headerlink" title="编辑器支持"></a>编辑器支持</h2><p>要使用 TypeScript 开发 Vue 应用程序，我们强烈建议您使用 Visual Studio Code，它为 TypeScript 提供了极好的“开箱即用”支持。如果你正在使用单文件组件 (SFC), 可以安装提供 SFC 支持以及其他更多实用功能的 Vetur 插件。</p>
<p>WebStorm 同样为 TypeScript 和 Vue 提供了“开箱即用”的支持。</p>
<h1 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h1><p>要让 TypeScript 正确推断 Vue 组件选项中的类型，您需要使用 Vue.component 或 Vue.extend 定义组件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">const</span> Component = Vue.extend(&#123;</span><br><span class="line">  <span class="comment">// 类型推断已启用</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Component = &#123;</span><br><span class="line">  <span class="comment">// 这里不会有类型推断，</span></span><br><span class="line">  <span class="comment">// 因为TypeScript不能确认这是Vue组件的选项</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="基于类的-Vue-组件"><a href="#基于类的-Vue-组件" class="headerlink" title="基于类的 Vue 组件"></a>基于类的 Vue 组件</h1><p>如果您在声明组件时更喜欢基于类的 API，则可以使用官方维护的 vue-class-component 装饰器：</p>
<figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="type">Vue</span> from <span class="symbol">'vu</span>e'</span><br><span class="line"><span class="keyword">import</span> <span class="type">Component</span> from <span class="symbol">'vue</span>-<span class="class"><span class="keyword">class</span><span class="title">-component</span>'</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">//</span> <span class="title">@Component</span> <span class="title">修饰符注明了此类为一个</span> <span class="title">Vue</span> <span class="title">组件</span></span></span><br><span class="line"><span class="class"><span class="title">@Component</span>(<span class="params">&#123;</span></span></span><br><span class="line"><span class="class"><span class="params">  // 所有的组件选项都可以放在这里</span></span></span><br><span class="line"><span class="class"><span class="params">  template: '&lt;button @click="onClick"&gt;<span class="type">Click</span>!&lt;/button&gt;'</span></span></span><br><span class="line"><span class="class"><span class="params">&#125;</span>)</span></span><br><span class="line"><span class="class"><span class="title">export</span> <span class="title">default</span> <span class="title">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Vue</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 初始数据可以直接声明为实例的属性</span></span><br><span class="line">  message: string = <span class="symbol">'Hello</span>!'</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 组件方法也可以直接声明为实例的方法</span></span><br><span class="line">  onClick (): void &#123;</span><br><span class="line">    window.alert(<span class="keyword">this</span>.message)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="增强类型以配合插件使用"><a href="#增强类型以配合插件使用" class="headerlink" title="增强类型以配合插件使用"></a>增强类型以配合插件使用</h1><p>插件可以增加 Vue 的全局/实例属性和组件选项。在这些情况下，在 TypeScript 中制作插件需要类型声明。庆幸的是，TypeScript 有一个特性来补充现有的类型，叫做模块补充 (module augmentation)。</p>
<p>例如，声明一个 string 类型的实例属性 $myProperty：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 确保在声明补充的类型之前导入 'vue'</span></span><br><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 定制一个文件，设置你想要补充的类型</span></span><br><span class="line"><span class="comment">//    在 types/vue.d.ts 里 Vue 有构造函数类型</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> 'vue/types/vue' &#123;</span><br><span class="line"><span class="comment">// 3. 声明为 Vue 补充的东西</span></span><br><span class="line">  <span class="keyword">interface</span> Vue &#123;</span><br><span class="line">    $myProperty: <span class="built_in">string</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在你的项目中包含了上述作为声明文件的代码之后 (像 my-property.d.ts)，你就可以在 Vue 实例上使用 $myProperty 了。</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">var</span> vm = <span class="literal">new</span> Vue()</span><br><span class="line">console.<span class="keyword">log</span>(vm.$myProperty) <span class="comment">// 将会顺利编译通过</span></span><br></pre></td></tr></table></figure>
<p>你也可以声明额外的属性和组件选项：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> 'vue/types/vue' &#123;</span><br><span class="line">  <span class="comment">// 可以使用 `VueConstructor` 接口</span></span><br><span class="line">  <span class="comment">// 来声明全局属性</span></span><br><span class="line">  <span class="keyword">interface</span> VueConstructor &#123;</span><br><span class="line">    $myGlobal: <span class="built_in">string</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ComponentOptions 声明于 types/options.d.ts 之中</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> 'vue/types/options' &#123;</span><br><span class="line">  <span class="keyword">interface</span> ComponentOptions&lt;V <span class="keyword">extends</span> Vue&gt; &#123;</span><br><span class="line">    myOption?: <span class="built_in">string</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上述的声明允许下面的代码顺利编译通过：</p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 全局属性</span></span><br><span class="line">console.<span class="keyword">log</span>(Vue.$myGlobal)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 额外的组件选项</span></span><br><span class="line"><span class="built_in">var</span> vm = <span class="literal">new</span> Vue(&#123;</span><br><span class="line">  myOption: <span class="string">'Hello'</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<h1 id="标注返回值"><a href="#标注返回值" class="headerlink" title="标注返回值"></a>标注返回值</h1><p>因为 Vue 的声明文件天生就具有循环性，TypeScript 可能在推断某个方法的类型的时候存在困难。因此，你可能需要在 render 或 computed 里的方法上标注返回值。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue, &#123; VNode &#125; from <span class="string">'vue'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Component = Vue.extend(&#123;</span><br><span class="line">  <span class="keyword">data</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      msg: <span class="string">'Hello'</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    <span class="comment">// 需要标注有 `this` 参与运算的返回值类型</span></span><br><span class="line">    greet (): string &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.msg + <span class="string">' world'</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  computed: &#123;</span><br><span class="line">    <span class="comment">// 需要标注</span></span><br><span class="line">    greeting(): string &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.greet() + <span class="string">'!'</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// `createElement` 是可推导的，但是 `render` 需要返回值类型</span></span><br><span class="line">  render (createElement): VNode &#123;</span><br><span class="line">    <span class="keyword">return</span> createElement(<span class="string">'div'</span>, <span class="keyword">this</span>.greeting)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>如果你发现类型推导或成员补齐不工作了，标注某个方法也许可以帮助你解决这个问题。使用 –noImplicitAny 选项将会帮助你找到这些未标注的方法。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/24/Axios的拦截器原理以及请求等待重发的实现/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/24/Axios的拦截器原理以及请求等待重发的实现/" itemprop="url">Axios的拦截器原理以及请求等待重发的实现</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-24T13:55:27+08:00">
                2018-09-24
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/javascript/" itemprop="url" rel="index">
                    <span itemprop="name">javascript</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <h1 id="回顾下Promise"><a href="#回顾下Promise" class="headerlink" title="回顾下Promise"></a>回顾下Promise</h1><ol>
<li><p>Promise的基础知识不做多介绍可以参考两个文章</p>
<ol>
<li><a href="http://es6.ruanyifeng.com/#docs/promise" target="_blank" rel="noopener">《ECMAScript 6 入门》：Promise 对象</a></li>
<li><a href="https://www.cnblogs.com/JuFoFu/p/6692055.html" target="_blank" rel="noopener">ES6 Promise的resolved深入理解</a> 这个是我看到的对于Promise状态解释比较清晰的一个文章</li>
</ol>
</li>
<li><p>Promise的状态</p>
<ol>
<li>Promise状态一旦改变就不能再变，一直保持此状态</li>
<li>Promise可以被其他Promise锁定—-这个很重要，跟后面的要说到的Axios的请求3阻塞等待有关系</li>
<li>一个重要的Demo<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Promise</span>.resolve(</span><br><span class="line">    <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve,reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'inner Promise'</span>);</span><br><span class="line">        resolve(<span class="string">'123'</span>);</span><br><span class="line">    &#125;).then(<span class="function"><span class="params">data</span>=&gt;</span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="number">1</span>,<span class="keyword">typeof</span>(data), data);</span><br><span class="line">        <span class="keyword">return</span> data+<span class="string">'4'</span>;</span><br><span class="line">    &#125;)</span><br><span class="line">).then(<span class="function"><span class="params">data</span>=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(<span class="string">'Randy'</span>+data);</span><br><span class="line">&#125;).then(<span class="function"><span class="params">data</span>=&gt;</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>,<span class="keyword">typeof</span>(data), data)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
</li>
</ol>
<ul>
<li>输出如下结果<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">inner <span class="built_in">Promise</span></span><br><span class="line"><span class="string">"string"</span> <span class="string">"123"</span></span><br><span class="line"><span class="string">"string"</span> <span class="string">"Randy1234"</span></span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
<li><p>简单解释下上面的结果</p>
<ol>
<li><code>Promise.resolve</code>创建一个<code>Promise</code>对象，依赖于inner的Promise的resolve结果</li>
<li>内部的<code>new Promise().then()</code>创建了一个Promise，<code>new Promise()resolve</code>的结果是123，then()将结果改为1234，打印”string” “123”，然后返回’1234’这个作为外层的resolve结果</li>
<li>外层中第一个then()返回了一个Promise返回”Randy1234”作为resolve结果</li>
<li>外层中第二个then()接收到前一个的返回值，然后打印”string” “Randy1234”</li>
</ol>
</li>
<li><p>人话描述下这里用到的几个知识点</p>
<ol>
<li>Promise.resolve(data)等于new Promise(resolve=&gt;{resolve(data)})</li>
<li>Promise A可以使用另一个Promise B的resolve值作为自己的resolve值进入A的调用链</li>
<li>then()可以对处理结果进行修改</li>
</ol>
</li>
</ol>
<h1 id="Axios"><a href="#Axios" class="headerlink" title="Axios"></a>Axios</h1><p>接下来开始整体，说说Axios。Axios是基于Promise机制实现的异步的链式请求框架。体积小，源码易懂。非常适合做基础的请求库。</p>
<h1 id="Axios结构"><a href="#Axios结构" class="headerlink" title="Axios结构"></a>Axios结构</h1><ol>
<li><p>代码结构</p>
<ol>
<li>axios.js：入口文件，将Axios实例的request函数绑定为入口函数，axios.create其实返回的是一个function，就是Axios实例的Axios.prototype.request</li>
<li>lib/Axios.js：真正的Axios的实例，用于拼接拦截器的调用链，关键代码如下：</li>
</ol>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Hook up interceptors middleware</span></span><br><span class="line"><span class="keyword">var</span> chain = [dispatchRequest, <span class="literal">undefined</span>];</span><br><span class="line"><span class="keyword">var</span> promise = <span class="built_in">Promise</span>.resolve(config);</span><br><span class="line"></span><br><span class="line"><span class="keyword">this</span>.interceptors.request.forEach(<span class="function"><span class="keyword">function</span> <span class="title">unshiftRequestInterceptors</span>(<span class="params">interceptor</span>) </span>&#123;</span><br><span class="line">chain.unshift(interceptor.fulfilled, interceptor.rejected);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">this</span>.interceptors.response.forEach(<span class="function"><span class="keyword">function</span> <span class="title">pushResponseInterceptors</span>(<span class="params">interceptor</span>) </span>&#123;</span><br><span class="line">chain.push(interceptor.fulfilled, interceptor.rejected);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (chain.length) &#123;</span><br><span class="line">promise = promise.then(chain.shift(), chain.shift());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> promise;</span><br></pre></td></tr></table></figure>
<ol start="3">
<li><code>lib/InterceptorManager.js</code>：拦截器管理，是一个对[]的封装</li>
<li><code>lib/dispatchRequest.js</code>：发送请求的Promise，完成发送请求的逻辑。注意看<code>Axios.js</code>中的<code>var chain = [dispatchRequest, undefined];</code></li>
<li><code>adapter/*</code>：适配器，这里的代码保证了Axios在ssr模式下和浏览器环境中区分环境实现请求返送的逻辑。里面存放了两个定义好的适配器，可以参照README.md中的描述自定义适配器</li>
</ol>
</li>
<li><p>拦截器模型</p>
</li>
</ol>
<p><img src="/blog/2018/09/24/Axios的拦截器原理以及请求等待重发的实现/5637148-48325488140d9304.png" alt="Axios拦截器示意图.png"></p>
<ul>
<li>request和response的拦截器都可以有多对，其中每一个点都会挂在一个then()的调用上，<code>promise.then(chain.shift(), chain.shift());</code></li>
</ul>
<h1 id="使用场景：应对OAuth中refresh-token换access-token时其他请求需等待的问题"><a href="#使用场景：应对OAuth中refresh-token换access-token时其他请求需等待的问题" class="headerlink" title="使用场景：应对OAuth中refresh_token换access_token时其他请求需等待的问题"></a>使用场景：应对OAuth中refresh_token换access_token时其他请求需等待的问题</h1><ul>
<li><p>根据场景来看，我们需要有一下几个能力</p>
<ol>
<li>Request拦截器中任意的请求(比如请求A)进入之后，如果主动检测到了access_token的超时，那么停止当前请求A，开启refresh_token的请求，当成功之后再执行A请求</li>
<li>当请求已发送，服务端识别到了token失效，Response拦截器中的处理跟Request拦截器要做的事一样</li>
<li>当有进行中的refresh_token请求时，此请求需要等待这个进行中的refresh_token的请求成功之后再进行发送</li>
</ol>
<ul>
<li><p>那我们一个一个来处理</p>
<ul>
<li><p>当请求进入拦截器，主动发现需要refresh_token时(比如access_token有效期临近)需要将请求放置在refresh_token成功之后</p>
</li>
<li><p>处理方式可以采用在then()调用拦截器的方法时返回一个Promise，然后在Promise中等待refresh_token的请求成功之后再进行当前进入的请求的发送</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">   <span class="comment">// axios 的 request拦截器</span></span><br><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 模拟等待refresh_token</span></span><br><span class="line">        setTimeout(<span class="function"><span class="keyword">function</span> (<span class="params">config_param</span>) </span>&#123;</span><br><span class="line">            resolve(config_param);</span><br><span class="line">        &#125;, <span class="number">2000</span>, config)</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<ul>
<li>上面的代码只是一个简单的示意，实际处理中要注意以下几点，<ul>
<li>刷新token之后config_param要处理新Token的拼装；</li>
<li>请求拦截器中要能识别出是否是refresh_token的请求；</li>
<li>能识别出是否正在进行refresh_token，并能正确处理其他进入的请求，这个后面会讲到</li>
</ul>
</li>
<li>处理之后调用链会变成这样</li>
</ul>
<p>   <img src="/blog/2018/09/24/Axios的拦截器原理以及请求等待重发的实现/5637148-37f4199fd75e8492.png" alt="请求拦截器中加入Promise"></p>
<ol start="2">
<li><p>当请求已发送，服务端识别到了Token失效时(这个情况比较多，服务器时间与本地有间隙；Token不支持多点登陆等等)，需要先refresh_token，然后重发请求</p>
</li>
<li><p>可以采用与Request拦截器相似的处理，在拦截器中同样开启refresh_token，成功之后重新创建已经失败的请求，执行完请求之后将重新创建的请求获取到的返回值resolve给response的返回值</p>
</li>
</ol>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> res = response.data;</span><br><span class="line"><span class="keyword">switch</span> (res.code) &#123;</span><br><span class="line">    <span class="keyword">case</span> RespStatus.UNAUTHORIZED.code: &#123;</span><br><span class="line">        <span class="keyword">let</span> respConfig = response.config;</span><br><span class="line">        <span class="keyword">if</span> (isRefreshTokenReq(respConfig.url)) &#123;</span><br><span class="line">            <span class="comment">//刷新Token的请求如果出现401直接退出登录</span></span><br><span class="line">            showLoginOut();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            logDebug(<span class="string">'请求的返回值出现401，由请求'</span> + config.url + <span class="string">'的返回值触发，开始进行refresh_token！'</span>);</span><br><span class="line">            <span class="keyword">let</span> auth = storage.state.user.auth;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                res = doRefreshToken(auth.refresh_token, auth.wmq_d_current_username, respConfig)</span><br><span class="line">                    .then(<span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">                        <span class="keyword">return</span> wmqhttp(attachAuthInfoToConfig(storage.state.user.auth, config));</span><br><span class="line">                    &#125;).then(<span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(value);</span><br><span class="line">                    &#125;);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">                <span class="built_in">console</span>.log(<span class="string">'无法等待刷新Token！'</span>, e);</span><br><span class="line">                showLoginOut();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        logDebug(<span class="string">'Axios response default data：'</span>, res);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> res;</span><br></pre></td></tr></table></figure>
<p>处理之后调用链会变成这样</p>
<p>响应拦截器中加入Promise和二次请求<br>对于在refresh_token时其他请求的进入需要安排这个请求动作，让请求发生在refresh_token之后进行</p>
<ul>
<li>解决思路如下，在全局的状态中记录是否正在刷新请求，并且保存refresh_token的Promise。当遇到请求之后新创建一个Promise交给拦截器，在新创建的Promise中用then()等待refresh_token。</li>
</ul>
<pre><code>new Promise(resolve =&gt; {
    pendingPromise.then(() =&gt; {
        logDebug(&apos;刷新Token成功，开始处理之前等待的请求&apos;, config.url);
        resolve(attachAuthInfoToConfig(storage.state.user.auth, config));
    });
});
</code></pre>
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/24/关于-React-Router-4-的一切/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/blog/2018/09/24/关于-React-Router-4-的一切/" itemprop="url">关于 React Router 4 的一切</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-24T11:23:52+08:00">
                2018-09-24
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/react/" itemprop="url" rel="index">
                    <span itemprop="name">react</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <p>本文的意图并不是重复 React Router 4 已经写得很好的文档。我将介绍最常见的 API，但真正的重点是我发现的成功模式和策略。</p>
<p>对于本文，以下是一些你需要熟悉的 JavaScript 概念:</p>
<ul>
<li>React （无状态）函数组件</li>
<li>ES2015 箭头函数 以及它们的“隐式返回”</li>
<li>ES2015 解构</li>
<li>ES2015 模板字符串<br>如果你喜欢跳转到演示区的话，请点这里：</li>
</ul>
<p><a href="https://codepen.io/bradwestfall/project/editor/XWNWge?preview_height=50&amp;open_file=src/app.js" target="_blank" rel="noopener">查看演示</a></p>
<h1 id="新的-API-和新的思维模式"><a href="#新的-API-和新的思维模式" class="headerlink" title="新的 API 和新的思维模式"></a>新的 API 和新的思维模式</h1><p>React Router 的早期版本将路由规则集中在一个位置，使它们与布局组件分离。当然，路由可以被划分成多个文件，但从概念上讲，路由是一个单元，基本上是一个美化的配置文件。</p>
<p>或许了解 v4 不同之处的最好方法是用每个版本编写一个简单的两页应用程序并进行比较。示例应用程序只有两个路由，对应首页和用户页面。</p>
<p><strong>这里是 v3 的：</strong><br><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml">import </span><span class="template-variable">&#123; Router, Route, IndexRoute &#125;</span><span class="xml"> from 'react-router'</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const PrimaryLayout = props =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-layout"</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml">      Our React Router 3 App</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">main</span>&gt;</span></span></span><br><span class="line"><span class="xml">      </span><span class="template-variable">&#123;props.children&#125;</span><span class="xml"></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const HomePage =() =&gt; <span class="tag">&lt;<span class="name">div</span>&gt;</span>Home Page<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">const UsersPage = () =&gt; <span class="tag">&lt;<span class="name">div</span>&gt;</span>Users Page<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const App = () =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">Router</span> <span class="attr">history</span>=</span></span><span class="template-variable">&#123;browserHistory&#125;</span><span class="xml"><span class="tag">&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/"</span> <span class="attr">component</span>=</span></span><span class="template-variable">&#123;PrimaryLayout&#125;</span><span class="xml"><span class="tag">&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">IndexRoute</span> <span class="attr">component</span>=</span></span><span class="template-variable">&#123;HomePage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">component</span>=</span></span><span class="template-variable">&#123;UsersPage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">Route</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">render(<span class="tag">&lt;<span class="name">App</span> /&gt;</span>, document.getElementById('root'))</span></span><br></pre></td></tr></table></figure></p>
<p>以下是 v3 中的一些核心思想，但在 v4 中是不正确的:</p>
<p>路由集中在一个地方。<br>布局和页面嵌套是通过 &lt;Route> 组件的嵌套而来的。<br>布局和页面组件是完全纯粹的，它们是路由的一部分。<br>React Router 4 不再主张集中式路由了。相反，路由规则位于布局和 UI 本身之间。例如，以下是<strong> v4 中的相同的应用程序：</strong><br><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml">import </span><span class="template-variable">&#123; BrowserRouter, Route &#125;</span><span class="xml"> from 'react-router-dom'</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const PrimaryLayout = () =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-layout"</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml">      Our React Router 4 App</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">main</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/"</span> <span class="attr">exact</span> <span class="attr">component</span>=</span></span><span class="template-variable">&#123;HomePage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">component</span>=</span></span><span class="template-variable">&#123;UsersPage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const HomePage =() =&gt; <span class="tag">&lt;<span class="name">div</span>&gt;</span>Home Page<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">const UsersPage = () =&gt; <span class="tag">&lt;<span class="name">div</span>&gt;</span>Users Page<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const App = () =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">BrowserRouter</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">PrimaryLayout</span> /&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">BrowserRouter</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">render(<span class="tag">&lt;<span class="name">App</span> /&gt;</span>, document.getElementById('root'))</span></span><br></pre></td></tr></table></figure></p>
<p>新的 API 概念：由于我们的应用程序是用于浏览器的，所以我们需要将它封装在来自 v4 的 BrowserRouter 中。还要注意的是我们现在从 react-router-dom 中导入它（这意味着我们安装的是 react-router-dom 而不是 react-router）。提示！现在叫做 react-router-dom 是因为还有一个 native 版本。</p>
<p>对于使用 React Router v4 构建的应用程序，首先看到的是“路由”似乎丢失了。在 v3 中，路由是我们的应用程序直接呈现给 DOM 的最巨大的东西。 现在，除了 &lt;BrowserRouter>外，我们首先抛给 DOM 的是我们的应用程序本身。</p>
<p>另一个在 v3 的例子中有而在 v4 中没有的是，使用 {props.children} 来嵌套组件。这是因为在 v4 中，&lt;Route> 组件在何处编写，如果路由匹配，子组件将在那里渲染。</p>
<h1 id="包容性路由"><a href="#包容性路由" class="headerlink" title="包容性路由"></a>包容性路由</h1><p>在前面的例子中，你可能已经注意到了 exact 这个属性。那么它是什么呢？V3 的路由规则是“排他性”的，这意味着只有一条路由将获胜。V4 的路由默认为“包含”的，这意味着多个 &lt;Route> 可以同时进行匹配和渲染。</p>
<p>在上一个例子中，我们试图根据路径渲染 HomePage 或者 UsersPage。如果从示例中删除了 exact 属性，那么在浏览器中访问 /users 时，HomePage 和 UsersPage 组件将同时被渲染。</p>
<p>要更好地了解匹配逻辑，请查看 path-to-regexp，这是 v4 现在正在使用的，以确定路由是否匹配 URL。</p>
<p>为了演示包容性路由是有帮助的，我们在标题中包含一个 UserMenu，但前提是我们在应用程序的用户部分：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">const PrimaryLayout = () =&gt; (</span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-layout"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">header</span>&gt;</span></span><br><span class="line">      Our React Router 4 App</span><br><span class="line">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">component</span>=<span class="string">&#123;UsersMenu&#125;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">header</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;HomePage&#125;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">component</span>=<span class="string">&#123;UsersPage&#125;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p>现在，当用户访问 /users 时，两个组件都会渲染。类似这样的事情在 v3 中通过特定的匹配模式也是可行的，但它更复杂。得益于 v4 的包容性路由，现在能够很轻松地实现。</p>
<h1 id="排他性路由"><a href="#排他性路由" class="headerlink" title="排他性路由"></a>排他性路由</h1><p>如果你只需要在路由列表里匹配一个路由，则使用 &lt;Switch> 来启用排他路由：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">const PrimaryLayout = () =&gt; (</span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-layout"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">PrimaryHeader</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;HomePage&#125;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users/add"</span> <span class="attr">component</span>=<span class="string">&#123;UserAddPage&#125;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">component</span>=<span class="string">&#123;UsersPage&#125;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Redirect</span> <span class="attr">to</span>=<span class="string">"/"</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p>在给定的 &lt;Switch> 路由中只有一条将渲染。在 HomePage 路由上，我们仍然需要 exact属性，尽管我们会先把它列出来。否则，当访问诸如 /users 或 /users/add 的路径时，主页路由也将匹配。事实上，战略布局是使用排他路由策略（因为它总是像传统路由那样使用）时的关键。请注意，我们在 /users 之前策略性地放置了 /users/add 的路由，以确保正确匹配。由于路径 /users/add 将匹配 /users 和 /users/add，所以最好先把 /users/add 放在前面。</p>
<p>当然，如果我们以某种方式使用 exact，我们可以把它们放在任何顺序上，但至少我们有选择。</p>
<p>如果遇到，&lt;Redirect> 组件将会始终执行浏览器重定向，但是当它位于 &lt;Switch> 语句中时，只有在其他路由不匹配的情况下，才会渲染重定向组件。想了解在非切换环境下如何使用 &lt;Redirect>，请参阅下面的授权路由。</p>
<p><strong>“默认路由”和“未找到”</strong></p>
<p>尽管在 v4 中已经没有 <indexroute> 了，但可以使用 <route exact> 来达到同样的效果。如果没有路由解析，则可以使用 <switch> 与 <redirect> 重定向到具有有效路径的默认页面（如同我对本示例中的 HomePage 所做的），甚至可以是一个“未找到页面”。</redirect></switch></route></indexroute></p>
<p><strong>嵌套布局</strong><br>你可能开始期待嵌套子布局，以及如何实现它们。我原本不认为我会纠结这个概念，但我确实纠结了。React Router v4 给了我们很多选择，这使它变得很强大。但是，选择意味着有选择不理想策略的自由。表面上看，嵌套布局很简单，但根据你的选择，可能会因为你组织路由的方式而遇到阻碍。</p>
<p>为了演示，假设我们想扩展我们的用户部分，所以我们会有一个“用户列表”页面和一个“用户详情”页面。我们也希望产品也有类似的页面。用户和产品都需要其个性化的子布局。例如，每个可能都有不同的导航选项卡。有几种方法可以解决这个问题，有的好，有的不好。第一种方法不是很好，但我想告诉你，这样你就不会掉入这个陷阱。第二种方法要好很多。</p>
<p>第一种方法，我们修改 PrimaryLayout，以适应用户和产品对应的列表及详情页面：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">const PrimaryLayout = props =&gt; &#123;</span><br><span class="line">  return (</span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-layout"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">PrimaryHeader</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;HomePage&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;BrowseUsersPage&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users/:userId"</span> <span class="attr">component</span>=<span class="string">&#123;UserProfilePage&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/products"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;BrowseProductsPage&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/products/:productId"</span> <span class="attr">component</span>=<span class="string">&#123;ProductProfilePage&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Redirect</span> <span class="attr">to</span>=<span class="string">"/"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>虽然这在技术上可行的，但仔细观察这两个用户页面就会发现问题：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> BrowseUsersPage = <span class="function"><span class="params">()</span> =&gt;</span> (</span><br><span class="line">  &lt;div className=<span class="string">"user-sub-layout"</span>&gt;</span><br><span class="line">    &lt;aside&gt;</span><br><span class="line">      &lt;UserNav /&gt;</span><br><span class="line">    &lt;<span class="regexp">/aside&gt;</span></span><br><span class="line"><span class="regexp">    &lt;div className="primary-content"&gt;</span></span><br><span class="line"><span class="regexp">      &lt;BrowseUserTable /</span>&gt;</span><br><span class="line">    &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">  &lt;/</span>div&gt;</span><br><span class="line">)</span><br><span class="line"> </span><br><span class="line"><span class="keyword">const</span> UserProfilePage = <span class="function"><span class="params">props</span> =&gt;</span> (</span><br><span class="line">  &lt;div className=<span class="string">"user-sub-layout"</span>&gt;</span><br><span class="line">    &lt;aside&gt;</span><br><span class="line">      &lt;UserNav /&gt;</span><br><span class="line">    &lt;<span class="regexp">/aside&gt;</span></span><br><span class="line"><span class="regexp">    &lt;div className="primary-content"&gt;</span></span><br><span class="line"><span class="regexp">      &lt;UserProfile userId=&#123;props.match.params.userId&#125; /</span>&gt;</span><br><span class="line">    &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">  &lt;/</span>div&gt;</span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p>新 API 概念：props.match 被赋到由 &lt;Route> 渲染的任何组件。你可以看到，userId 是由 props.match.params 提供的，了解更多请参阅 v4 文档。或者，如果任何组件需要访问 props.match，而这个组件没有由 &lt;Route> 直接渲染，那么我们可以使用 withRouter() 高阶组件。</p>
<p>每个用户页面不仅要渲染其各自的内容，而且还必须关注子布局本身（并且每个子布局都是重复的）。虽然这个例子很小，可能看起来微不足道，但重复的代码在一个真正的应用程序中可能是一个问题。更不用说，每次 BrowseUsersPage 或 UserProfilePage 被渲染时，它将创建一个新的 UserNav 实例，这意味着所有的生命周期方法都将重新开始。如果导航标签需要初始网络流量，这将导致不必要的请求 —— 这都是我们决定使用路由的方式造成的。</p>
<p>这里有另一种更好的方法：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">const PrimaryLayout = props =&gt; &#123;</span><br><span class="line">  return (</span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-layout"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">PrimaryHeader</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;HomePage&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">component</span>=<span class="string">&#123;UserSubLayout&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/products"</span> <span class="attr">component</span>=<span class="string">&#123;ProductSubLayout&#125;</span> /&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">Redirect</span> <span class="attr">to</span>=<span class="string">"/"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>与每个用户和产品页面相对应的四条路由不同，我们为每个部分的布局提供了两条路由。</p>
<p>请注意，上述示例没有使用 exact 属性，因为我们希望 /users 匹配任何以 /users 开头的路由，同样适用于产品。</p>
<p>通过这种策略，渲染其它路由将成为子布局的任务。UserSubLayout 可能如下所示：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">const UserSubLayout = () =&gt; (</span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"user-sub-layout"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">aside</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">UserNav</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">aside</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-content"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users"</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;BrowseUsersPage&#125;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">"/users/:userId"</span> <span class="attr">component</span>=<span class="string">&#123;UserProfilePage&#125;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p>新策略中最明显的胜出在于所有用户页面之间的不重复布局。这是一个双赢，因为它不会像第一个示例那样具有相同生命周期的问题。</p>
<p>有一点需要注意的是，即使我们在布局结构中深入嵌套，路由仍然需要识别它们的完整路径才能匹配。为了节省重复输入（以防你决定将“用户”改为其他内容），请改用 props.match.path：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">const UserSubLayout = props =&gt; (</span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"user-sub-layout"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">aside</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">UserNav</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">aside</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-content"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#123;props.match.path&#125;</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;BrowseUsersPage&#125;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">props.match.path</span>&#125;/<span class="attr">:userId</span>`&#125; <span class="attr">component</span>=<span class="string">&#123;UserProfilePage&#125;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<h1 id="匹配"><a href="#匹配" class="headerlink" title="匹配"></a>匹配</h1><p>到目前为止，props.match 对于知道详情页面渲染的 userId 以及如何编写我们的路由是很有用的。match 对象给我们提供了几个属性，包括 match.params、match.path、match.url 和其他几个。</p>
<p><strong>match.path vs match.url</strong><br>起初这两者之间的区别似乎并不清楚。控制台日志有时会显示相同的输出，这使得它们之间的差异更加模糊。例如，当浏览器路径为 /users 时，它们在控制台日志将输出相同的值：<br><figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">const UserSubLayout = (&#123; match &#125;) =&gt; &#123;</span><br><span class="line">  console.log(match.url)   // 输出：<span class="string">"/users"</span></span><br><span class="line">  console.log(match<span class="built_in">.path</span>)  // 输出：<span class="string">"/users"</span></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"user-sub-layout"</span>&gt;</span></span></span><br><span class="line"><span class="xml">      </span><span class="xml"><span class="tag">&lt;<span class="name">aside</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">UserNav</span> /&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;/<span class="name">aside</span>&gt;</span></span><span class="xml"></span></span><br><span class="line"><span class="xml">      </span><span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-content"</span>&gt;</span></span></span><br><span class="line"><span class="xml">        </span><span class="xml"><span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span></span><br><span class="line"><span class="xml">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="xquery">&#123;match<span class="built_in">.path</span>&#125;</span><span class="xml"><span class="tag"> <span class="attr">exact</span> <span class="attr">component</span>=</span></span><span class="xquery">&#123;BrowseUsersPage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="xquery">&#123;`$&#123;match<span class="built_in">.path</span>&#125;</span><span class="xml"><span class="tag">/<span class="attr">:userId</span>`&#125; <span class="attr">component</span>=</span></span><span class="xquery">&#123;UserProfilePage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span><span class="xml"></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><span class="xml"></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>ES2015 概念： match 在组件函数的参数级别将被解构。</strong></p>
<p>虽然我们看不到差异，但 match.url 是浏览器 URL 中的实际路径，而 match.path 是为路由编写的路径。这就是为什么它们是一样的，至少到目前为止。但是，如果我们更进一步，在 UserProfilePage 中进行同样的控制台日志操作，并在浏览器中访问 /users/5，那么 match.url 将是 “/users/5” 而 match.path 将是 “/users/:userId”。</p>
<p><strong>选择哪一个？</strong><br>如果你要使用其中一个来帮助你构建路由路径，我建议你选择 match.path。使用 match.url 来构建路由路径最终会导致你不想看到的场景。下面是我遇到的一个情景。在一个像 UserProfilePage（当用户访问 /users/5 时渲染）的组件中，我渲染了如下这些子组件：<br><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml">const UserComments = (</span><span class="template-variable">&#123; match &#125;</span><span class="xml">) =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span>&gt;</span>UserId: </span><span class="template-variable">&#123;match.params.userId&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const UserSettings = (</span><span class="template-variable">&#123; match &#125;</span><span class="xml">) =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span>&gt;</span>UserId: </span><span class="template-variable">&#123;match.params.userId&#125;</span><span class="xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br><span class="line"><span class="xml"> </span></span><br><span class="line"><span class="xml">const UserProfilePage = (</span><span class="template-variable">&#123; match &#125;</span><span class="xml">) =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    User Profile:</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="template-variable">&#123;`$&#123;match.url&#125;</span><span class="xml"><span class="tag">/<span class="attr">comments</span>`&#125; <span class="attr">component</span>=</span></span><span class="template-variable">&#123;UserComments&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="template-variable">&#123;`$&#123;match.path&#125;</span><span class="xml"><span class="tag">/<span class="attr">settings</span>`&#125; <span class="attr">component</span>=</span></span><span class="template-variable">&#123;UserSettings&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br></pre></td></tr></table></figure></p>
<p>为了说明问题，我渲染了两个子组件，一个路由路径来自于 match.url，另一个来自 match.path。以下是在浏览器中访问这些页面时所发生的事情:</p>
<ul>
<li>访问 /users/5/comments 渲染 “UserId: undefined”。</li>
<li>访问 /users/5/settings 渲染 “UserId: 5”。<br>那么为什么 match.path 可以帮助我们构建路径 而 match.url 则不可以呢？答案就是这样一个事实：{${match.url}/comments} 基本上就像和硬编码的 {‘/users/5/comments’} 一样。这样做意味着后续组件将无法正确地填充 match.params，因为路径中没有参数，只有硬编码的 5。</li>
</ul>
<p>直到后来我看到文档的这一部分，才意识到它有多重要：</p>
<p><strong>match:</strong></p>
<ul>
<li>path - (string) 用于匹配路径模式。用于构建嵌套的 &lt;Route></li>
<li>url - (string) URL 匹配的部分。 用于构建嵌套的 &lt;Link></li>
</ul>
<p><strong>避免匹配冲突</strong><br>假设我们制作的应用程序是一个仪表版，所以我们希望能够通过访问 /users/add 和 /users/5/edit 来新增和编辑用户。但是在前面的例子中，users/:userId 已经指向了 UserProfilePage。那么这是否意味着带有users/:userId 的路由现在需要指向另一个子子布局来容纳编辑页面和详情页面？我不这么认为，因为编辑和详情页面共享相同的用户子布局，所以这个策略是可行的：<br><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml">const UserSubLayout = (</span><span class="template-variable">&#123; match &#125;</span><span class="xml">) =&gt; (</span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"user-sub-layout"</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">aside</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">UserNav</span> /&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">aside</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"primary-content"</span>&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">exact</span> <span class="attr">path</span>=</span></span><span class="template-variable">&#123;props.match.path&#125;</span><span class="xml"><span class="tag"> <span class="attr">component</span>=</span></span><span class="template-variable">&#123;BrowseUsersPage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="template-variable">&#123;`$&#123;match.path&#125;</span><span class="xml"><span class="tag">/<span class="attr">add</span>`&#125; <span class="attr">component</span>=</span></span><span class="template-variable">&#123;AddUserPage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="template-variable">&#123;`$&#123;match.path&#125;</span><span class="xml"><span class="tag">/<span class="attr">:userId</span>/<span class="attr">edit</span>`&#125; <span class="attr">component</span>=</span></span><span class="template-variable">&#123;EditUserPage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=</span></span><span class="template-variable">&#123;`$&#123;match.path&#125;</span><span class="xml"><span class="tag">/<span class="attr">:userId</span>`&#125; <span class="attr">component</span>=</span></span><span class="template-variable">&#123;UserProfilePage&#125;</span><span class="xml"><span class="tag"> /&gt;</span></span></span><br><span class="line"><span class="xml">      <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">)</span></span><br></pre></td></tr></table></figure></p>
<p>请注意，为了确保进行适当的匹配，新增和编辑路由需要战略性地放在详情路由之前。如果详情路径在前面，那么访问 /users/add 时将匹配详情（因为 “add” 将匹配 :userId）。</p>
<p>或者，如果我们这样创建路径 <code>${match.path}/:userId(\\d+)</code>，来确保 :userId 必须是一个数字，那么我们可以先放置详情路由。然后访问 /users/add 将不会产生冲突。这是我在 path-to-regexp 的文档中学到的技巧。</p>
<h1 id="授权路由"><a href="#授权路由" class="headerlink" title="授权路由"></a>授权路由</h1><p>在应用程序中，通常会根据用户的登录状态来限制用户访问某些路由。对于未经授权的页面（如“登录”和“忘记密码”）与已授权的页面（应用程序的主要部分）看起来不一样也是常见的。为了解决这些需求，需要考虑一个应用程序的主要入口点：<br><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;<span class="type">Provider</span> store=&#123;store&#125;&gt;</span><br><span class="line">        &lt;<span class="type">BrowserRouter</span>&gt;</span><br><span class="line">          &lt;<span class="type">Switch</span>&gt;</span><br><span class="line">            &lt;<span class="type">Route</span> path=<span class="string">"/auth"</span> component=&#123;<span class="type">UnauthorizedLayout</span>&#125; /&gt;</span><br><span class="line">            &lt;<span class="type">AuthorizedRoute</span> path=<span class="string">"/app"</span> component=&#123;<span class="type">PrimaryLayout</span>&#125; /&gt;</span><br><span class="line">          &lt;/<span class="type">Switch</span>&gt;</span><br><span class="line">        &lt;/<span class="type">BrowserRouter</span>&gt;</span><br><span class="line">      &lt;/<span class="type">Provider</span>&gt;</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>使用 react-redux 与 React Router v4 非常类似，就像之前一样，只需将 BrowserRouter包在 &lt;Provider> 中即可。</p>
<p>通过这种方法可以得到一些启发。第一个是根据我们所在的应用程序的哪个部分，在两个顶层布局之间进行选择。像访问 /auth/login 或 /auth/forgot-password 这样的路径会使用 UnauthorizedLayout —— 一个看起来适于这种情况的布局。当用户登录时，我们将确保所有路径都有一个 /app 前缀，它使用 AuthorizedRoute 来确定用户是否登录。如果用户在没有登录的情况下，尝试访问以 /app 开头的页面，那么将被重定向到登录页面。</p>
<p>虽然 AuthorizedRoute 不是 v4 的一部分，但是我在 v4 文档的帮助下自己写了。v4 中一个惊人的新功能是能够为特定的目的创建你自己的路由。它不是将 component 的属性传递给 &lt;Route>，而是传递一个 render 回调函数：<br><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AuthorizedRoute</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">  componentWillMount() &#123;</span><br><span class="line">    getLoggedUser()</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  render() &#123;</span><br><span class="line">    const &#123; component: <span class="type">Component</span>, pending, logged, ...rest &#125; = <span class="keyword">this</span>.props</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;<span class="type">Route</span> &#123;...rest&#125; render=&#123;props =&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> (pending) <span class="keyword">return</span> &lt;div&gt;<span class="type">Loading</span>...&lt;/div&gt;</span><br><span class="line">        <span class="keyword">return</span> logged</span><br><span class="line">          ? &lt;<span class="type">Component</span> &#123;...<span class="keyword">this</span>.props&#125; /&gt;</span><br><span class="line">          : &lt;<span class="type">Redirect</span> to=<span class="string">"/auth/login"</span> /&gt;</span><br><span class="line">      &#125;&#125; /&gt;</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line">const stateToProps = (&#123; loggedUserState &#125;) =&gt; (&#123;</span><br><span class="line">  pending: loggedUserState.pending,</span><br><span class="line">  logged: loggedUserState.logged</span><br><span class="line">&#125;)</span><br><span class="line"> </span><br><span class="line">export <span class="keyword">default</span> connect(stateToProps)(<span class="type">AuthorizedRoute</span>)</span><br></pre></td></tr></table></figure></p>
<p>可能你的登录策略与我的不同，我是使用网络请求来 getLoggedUser()，并将 pending 和 logged 插入 Redux 的状态中。pending 仅表示在路由中请求仍在继续。</p>
<p>点击此处查看 CodePen 上完整的身份验证示例。</p>
<p><strong>其他提示</strong><br>React Router v4 还有很多其他很酷的方面。最后，一定要提几件小事，以免到时它们让你措手不及。</p>
<p><strong>&lt;Link> vs &lt;NavLink></strong><br>在 v4 中，有两种方法可以将锚标签与路由集成：&lt;Link> 和 &lt;NavLink></p>
<p>&lt;NavLink> 与 &lt;Link> 一样，但如果 &lt;NavLink> 匹配浏览器的 URL，那么它可以提供一些额外的样式能力。例如，在示例应用程序中，有一个&lt;PrimaryHeader> 组件看起来像这样：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> PrimaryHeader = <span class="function"><span class="params">()</span> =&gt;</span> (</span><br><span class="line">  &lt;header className=<span class="string">"primary-header"</span>&gt;</span><br><span class="line">    &lt;h1&gt;Welcome to our app!<span class="xml"><span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">    &lt;nav&gt;</span><br><span class="line">      &lt;NavLink to=<span class="string">"/app"</span> exact activeClassName=<span class="string">"active"</span>&gt;Home&lt;<span class="regexp">/NavLink&gt;</span></span><br><span class="line"><span class="regexp">      &lt;NavLink to="/</span>app/users<span class="string">" activeClassName="</span>active<span class="string">"&gt;Users&lt;/NavLink&gt;</span></span><br><span class="line"><span class="string">      &lt;NavLink to="</span>/app/products<span class="string">" activeClassName="</span>active<span class="string">"&gt;Products&lt;/NavLink&gt;</span></span><br><span class="line"><span class="string">    &lt;/nav&gt;</span></span><br><span class="line"><span class="string">  &lt;/header&gt;</span></span><br><span class="line"><span class="string">)</span></span><br></pre></td></tr></table></figure></p>
<p>使用 <navlink> 可以让我给任何一个激活的链接设置一个 active 样式。而且，需要注意的是，我也可以给它们添加 exact 属性。如果没有 exact，由于 v4 的包容性匹配策略，那么在访问 /app/users 时，主页的链接将处于激活中。就个人经历而言，NavLink 带 exact属性等价于 v3 的 &lt;link>，而且更稳定。</navlink></p>
<p><strong>URL 查询字符串</strong><br>再也无法从 React Router v4 中获取 URL 的查询字符串了。在我看来，做这个决定是因为没有关于如何处理复杂查询字符串的标准。所以，他们决定让开发者去选择如何处理查询字符串，而不是将其作为一个选项嵌入到 v4 的模块中。这是一件好事。</p>
<p>就个人而言，我使用的是 query-string，它是由 sindresorhus 大神写的。</p>
<p><strong>动态路由</strong><br>关于 v4 最好的部分之一是几乎所有的东西（包括 &lt;Route>）只是一个 React 组件。路由不再是神奇的东西了。我们可以随时随地渲染它们。想象一下，当满足某些条件时，你的应用程序的整个部分都可以路由到。当这些条件不满足时，我们可以移除路由。甚至我们可以做一些疯狂而且很酷的递归路由。</p>
<p>因为它 Just Components™，React Router 4 更简单了。</p>

          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
  </section>

  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/blog/"><i class="fa fa-angle-left"></i></a><a class="page-number" href="/blog/">1</a><span class="page-number current">2</span><a class="page-number" href="/blog/page/3/">3</a><a class="page-number" href="/blog/page/4/">4</a><a class="extend next" rel="next" href="/blog/page/3/"><i class="fa fa-angle-right"></i></a>
  </nav>



          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      

      <section class="site-overview-wrap sidebar-panel sidebar-panel-active">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">lhweb</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/blog/archives/">
              
                  <span class="site-state-item-count">39</span>
                  <span class="site-state-item-name">posts</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/blog/categories/index.html">
                  <span class="site-state-item-count">6</span>
                  <span class="site-state-item-name">categories</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/blog/tags/index.html">
                  <span class="site-state-item-count">25</span>
                  <span class="site-state-item-name">tags</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/blog/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">lhweb</span>

  
</div>


  <div class="powered-by">Powered by <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a></div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">Theme &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/blog/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/blog/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/blog/js/src/motion.js?v=5.1.4"></script>



  
  

  

  


  <script type="text/javascript" src="/blog/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  

  

  
  

  

  

  

</body>
</html>
